How to Execute Code and Commands Using `workspace.runtime.exec` in the Cloudflare Computer SDK

The workspace.runtime.exec method is the primary entry point for running arbitrary shell commands inside a Cloudflare Computer workspace, returning an ExecHandle that provides methods to retrieve results, stream output, and manage the process lifecycle.

The workspace.runtime.exec API enables developers to execute arbitrary commands within a Computer workspace environment. Defined in packages/computer/src/workspace.ts at line 10, this method serves as the main interface to the runtime execution layer. When invoked, it constructs a RuntimeExecRequest envelope and forwards it via RPC to the appropriate backend infrastructure.

Understanding the Runtime Execution Architecture

The execution flow begins with the Workspace class, where runtime.exec accepts a command string and optional configuration object. According to the Cloudflare Computer source code, this method immediately delegates to the runtime layer defined in packages/computer/src/runtime/runtime.ts. The runtime builds the execution envelope and dispatches it across the RPC channel to the target backend—whether that be the worker-shell, a command-line process, or a stub implementation for testing.

The method returns a Promise that resolves to an ExecHandle object. This handle represents the live execution state and remains valid for the duration of the process lifecycle, allowing asynchronous interaction with running commands.

The ExecHandle Interface

Once you obtain an ExecHandle from workspace.runtime.exec, you gain access to several critical methods for process management:

  • result(): Resolves when the command completes, returning an object containing code, stdout, and stderr
  • stream(): Returns an async iterator yielding JSON-L events for real-time output monitoring
  • kill(): Terminates the running process (sends SIGKILL)
  • id: Property containing the unique execution identifier

Per the implementation in packages/computer/src/runtime/runtime.ts at line 64, the stream() method emits structured events including stdout data, stderr data, and exit notifications.

Executing Commands: Practical Examples

Basic Command Execution

To run a simple command and retrieve its exit status, invoke exec with the command string and await the result:

const exec = await workspace.runtime.exec("true");
const result = await exec.result();
// result → { code: 0, stdout: "", stderr: "" }

Capturing UTF-8 Output

Specify the encoding option to automatically decode bytes as UTF-8 strings rather than raw buffers:

const exec = await workspace.runtime.exec("printf 'hello world'", {
  encoding: "utf8",
});
const { stdout } = await exec.result();
// stdout === "hello world"

Piping Input to Commands

Send data to a process's standard input using the stdin option:

const exec = await workspace.runtime.exec("cat", {
  stdin: "piped input",
  encoding: "utf8",
});
const { stdout } = await exec.result();
// stdout === "piped input"

Streaming Real-Time Output

For long-running commands, consume output incrementally via the stream() method rather than waiting for completion:

const exec = await workspace.runtime.exec("yes | head -n 5", {
  encoding: "utf8",
});

for await (const event of exec.stream()) {
  // event format: { type: "stdout", data: "..." }
  if (event.type === "stdout") {
    console.log(event.data);
  }
}

Specifying Execution Backends

Force execution on a specific backend by providing the backend option. The routing logic resides in packages/computer/src/tools/exec.ts at line 205:

await workspace.runtime.exec("echo $CF_WORKER", {
  backend: "worker-shell",  // Routes to worker-shell backend
});

If omitted, the system uses the workspace's default backend configuration.

Canceling Running Processes

Terminate execution programmatically using the kill() method:

const exec = await workspace.runtime.exec("sleep 30");
await exec.kill();           // Sends SIGKILL signal
await exec.result();         // Promise rejects with termination error

Reusing Executions with IDs

Supply an optional id parameter to deduplicate executions or retrieve existing handles. As enforced in packages/computer/src/runtime/runtime.ts at line 369, IDs must be non-empty and cannot exceed 256 bytes:

// First call creates the execution
await workspace.runtime.exec("true", { id: "my-run" });

// Subsequent calls with identical ID reuse the existing handle
const exec2 = await workspace.runtime.exec("true", { id: "my-run" });

Configuration and Error Handling

ExecOptions Parameters

The workspace.runtime.exec method accepts an ExecOptions object supporting:

  • backend: Target execution environment (e.g., "worker-shell")
  • stdin: String or buffer to pipe into the process
  • encoding: Set to "utf8" for automatic string decoding
  • sync: Boolean for synchronous execution mode
  • id: String identifier for execution reuse (max 256 bytes)

Backend Routing

When the backend option is specified, the request routes through the logic in packages/computer/src/tools/exec.ts. If the specified backend is unavailable, the method rejects with a "no execution backend" error. The test suite at packages/computer/src/workspace.test.ts line 891 validates this error condition.

Error Handling and Timeouts

Unconfigured or missing backends trigger immediate promise rejection. For timeout management, implement logic around the ExecHandle or use shell-level timeout commands, as the core method focuses on execution dispatch rather than timeout enforcement.

Observability and Tracing

Every workspace.runtime.exec call automatically generates OpenTelemetry-compatible tracing spans. As implemented in packages/computer/src/observe.ts at line 10, the system creates:

  • A parent span named workspace.runtime.exec
  • A child span named workspace.runtime.exec.spawn

These spans integrate with Cloudflare's tracing infrastructure, providing visibility into command execution latency and backend selection.

Summary

  • workspace.runtime.exec (defined in packages/computer/src/workspace.ts) is the primary method for command execution in Computer workspaces
  • Returns an ExecHandle offering result(), stream(), and kill() methods for process interaction
  • Supports ExecOptions including backend routing, stdin piping, encoding selection, and execution id reuse
  • Routes requests through the runtime layer (packages/computer/src/runtime/runtime.ts) to appropriate backends via RPC
  • Automatically generates OpenTelemetry spans (workspace.runtime.exec) for observability integration
  • Enforces ID length constraints (≤256 bytes) and validates backend availability before execution

Frequently Asked Questions

How do I capture both stdout and stderr from a command?

Call await exec.result() on the ExecHandle returned by workspace.runtime.exec. The resolved object contains stdout, stderr, and code properties. When using encoding: "utf8", both streams return as decoded strings; otherwise, they return as raw buffers.

Can I execute commands synchronously using workspace.runtime.exec?

Yes, set the sync: true option in the ExecOptions object. However, the method still returns a Promise that resolves to an ExecHandle. The synchronous flag primarily affects how the runtime processes the execution request rather than making the JavaScript call blocking.

What happens if I specify an execution ID that already exists?

The runtime returns the existing ExecHandle for that ID rather than spawning a new process. As specified in packages/computer/src/runtime/runtime.ts, the ID must be unique per workspace and no longer than 256 bytes. This mechanism enables deduplication and allows multiple parts of your application to reference the same running process.

How does backend routing work if I don't specify a backend?

If the backend option is omitted, workspace.runtime.exec uses the workspace's default backend configuration. The routing logic in packages/computer/src/tools/exec.ts dispatches to this default, which typically resolves to the worker-shell or the environment's primary execution context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →