Skip to main content

Execute Commands

The Runloop shell APIs provide different methods for command execution to optimize for both performance and reliability.

Choosing the Right Method

MethodUse CaseBenefitsPythonTypeScript
Optimistic ExecuteMost scenariosOptimistic execution, automatic fallback, configurable timeoutexecuteexecuteAndAwaitCompletion
Simple ExecuteSimple commands in toolsSimplified logic, automatically waits for completion, cleaner codeexecute_and_await_completionexecuteAndAwaitCompletion
Async ExecuteAdvanced async workflowsFull control over async execution, multiple concurrent commandsexecute_asyncexecuteAsync
The execute endpoint is the recommended method for most command execution scenarios. It combines the best aspects of synchronous and asynchronous execution with optimistic execution that waits up to 15 seconds (configurable up to 60s) for command completion before falling back to an async polling pattern. Key benefits:
  • Single API call for fast commands (< 15s default)
  • Automatic fallback to async polling for longer operations
  • No API Gateway timeout risks
  • Maintains durability and correctness
  • Configurable wait time (up to 60s)
Helper Methods:
  • For quick commands, you can also use executeAndAwaitCompletion/execute_and_await_completion which simplifies the logic by automatically waiting for command completion.
Migration from execute_sync: The new execute endpoint is a drop-in replacement for execute_sync with improved reliability and performance. Simply replace execute_sync calls with execute - no other code changes required.
The execute endpoint is optimized for all command types. For short-lived commands (< 15s), it behaves like a synchronous call. For longer commands, it automatically uses async polling while maintaining reliability.
Using execute directly: When making calls to the execute endpoint, you must include a uuidv7 parameter in your request body to ensure proper execution tracking and idempotency.
curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/execute' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "echo Hello World",
    "uuidv7": "01234567-89ab-7def-0123-456789abcdef"
  }'

Asynchronous Commands (Advanced)

For specific use cases where you need explicit async control, execute_async remains available. This is useful when you have specific async workflow requirements or need to manage multiple long-running commands independently.
1

Launch an async command

curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/execute_async' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "while true; do echo 'Hello World'; sleep 1; done",
  }'
2

Retrieve the Status of the Async Command including the latest output

curl -X GET 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/executions/<EXECUTION_ID>' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json'
3

(Optionally) Stream output in real-time

You can stream stdout and stderr logs in real-time using Server-Sent Events (SSE) for async executions:
# Stream stdout
curl -X GET 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/executions/<EXECUTION_ID>/stream_stdout_updates' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Accept: text/event-stream'

# Stream stderr
curl -X GET 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/executions/<EXECUTION_ID>/stream_stderr_updates' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Accept: text/event-stream'
Streaming is ideal for long-running commands where you want to see output as it’s generated, such as builds, tests, or log tailing.
4

(Optionally) Send input to stdin for interactive commands

For commands that expect interactive input, you can send content to stdin while the execution is running:
curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/executions/<EXECUTION_ID>/send_std_in' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"input": "your input text\n"}'
This is useful for interactive commands that wait for user input, such as prompts, REPLs, or commands that read from stdin.
5

(Optionally) Kill the async command if needed

curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/executions/<EXECUTION_ID>/kill' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{}'

Isolated vs StatefulShells

By default, every Devbox command is run in an isolated shell. This means that each command is executed in a new shell session, and the state of the shell is not preserved between commands.
curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/execute' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"command": "echo Hello World"}'

Using Stateful Shells

Alternatively, you can use the shell_name parameter to use a ‘stateful’ shell. This means that the shell will maintain its state across commands including environment variables and working directory. As an example, let’s create a series of interdependent commands that need to be run in the same shell:
1

Check initial directory

curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/execute' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "pwd",
    "shell_name": "my-shell"
  }'
2

Create and enter new directory

curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/execute' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "mkdir mynewfolder && cd mynewfolder",
    "shell_name": "my-shell"
  }'
3

Verify new working directory is preserved!

curl -X POST 'https://api.runloop.ai/v1/devboxes/<YOUR_DEVBOX_ID>/execute' \
  -H "Authorization: Bearer $RUNLOOP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "pwd",
    "shell_name": "my-shell"
  }'
I