---
description: How the Sandbox SDK 1.0 preview runs commands — argv launches, process handles, and container lifetime.
title: Process execution
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt  
> Use this file to discover all available pages before exploring further.

# Process execution

Last updated Aug 7, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/sandbox/1-0-preview/processes/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Path to Sandbox SDK 1.0

This page documents process execution on `@cloudflare/sandbox@next`, the preview of Sandbox SDK 1.0\. For today's stable command and session behavior, refer to [Commands](https://developers.cloudflare.com/sandbox/api/commands/) and [Sessions](https://developers.cloudflare.com/sandbox/concepts/sessions/).

In the 1.0 preview, treat the sandbox as a computer you drive with explicit programs.

Each `exec()` starts a **new supervised process** from **argv**. The call resolves when launch succeeds (you receive a process handle with `id` and `pid` properties), not when the process exits. Each launch is independent: pass `cwd` and `env` when the process needs them, or put multi-step shell syntax in one explicit shell argv. For an interactive PTY, use the [terminal](https://developers.cloudflare.com/sandbox/1-0-preview/terminals/) API.

Long-running work often spans many short Worker requests. A process ID is enough only while the **same container** still has that process. Across idle stop, failure, or replace, store the full launch (command, options, and any app checkpoint) so you can start again. Refer to [Continue work across requests](#continue-work-across-requests).

## Sandbox ID, container, and process

Most applications use **one sandbox per user or task**:

```js
const sandbox = getSandbox(env.Sandbox, "user-123");
```

```ts
const sandbox = getSandbox(env.Sandbox, "user-123");
```

Three different things are in play:

| Term           | Meaning                                                                                                                                                                                                                               |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sandbox ID** | The stable string your app uses to find that sandbox again (for example "user-123").                                                                                                                                                  |
| **Container**  | The [Containers](https://developers.cloudflare.com/containers/) instance currently running work for that sandbox. Sandboxes run on containers. The sandbox ID is stable. The container instance behind it is not always the same one. |
| **Process**    | A program you start with exec() **inside the current container**. The handle and process.id mean “this program in this container,” not “this sandbox ID forever.”                                                                     |

**Same sandbox ID does not mean the same container.** Processes live only in the container that started them. After a new container serves that ID, start new processes — you do not resume the old ones. Full sandbox model: [Sandbox lifecycle](https://developers.cloudflare.com/sandbox/1-0-preview/lifecycle/).

## Command model

The command model changes in the 1.0 preview compared with the current stable package:

| Current stable package                          | 1.0 preview                                           |
| ----------------------------------------------- | ----------------------------------------------------- |
| exec(string) resolves when the command finishes | exec(argv) resolves when the process starts           |
| Default session can preserve cd / export        | Each launch is independent                            |
| startProcess / execStream for other shapes      | One process handle covers short and long-running work |

Use argv for a single binary:

```js
const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });
```

```ts
const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });
```

Use an explicit shell when you need shell syntax:

```js
const process = await sandbox.exec([
	"/bin/bash",
	"-lc",
	"cd /workspace/app && npm test",
]);
```

```ts
const process = await sandbox.exec([
	"/bin/bash",
	"-lc",
	"cd /workspace/app && npm test",
]);
```

Or pass `cwd` and `env` on the launch instead of relying on a previous command:

```js
const process = await sandbox.exec(["npm", "test"], {
	cwd: "/workspace/app",
	env: { NODE_ENV: "test" },
});
```

```ts
const process = await sandbox.exec(["npm", "test"], {
	cwd: "/workspace/app",
	env: { NODE_ENV: "test" },
});
```

Sandbox-wide values use `setEnvVars`. Refer to [Environment variables](https://developers.cloudflare.com/sandbox/1-0-preview/environment/).

## Process handles

`await sandbox.exec(argv)` returns a **process handle**:

| Capability | Members                                                                          |
| ---------- | -------------------------------------------------------------------------------- |
| Identity   | id, pid                                                                          |
| Observe    | status(), logs(), output(), waitForExit(), waitForLog(), waitForPort(), exitCode |
| Control    | kill(signal?) with a numeric signal (default 15)                                 |

Observation timeouts and `AbortSignal` values cancel **only that wait or stream**. They do not stop the process. Call `kill()` when you intend to stop it.

`exec(argv, { timeout })` sets a **remote lifetime** deadline. When the supervisor stops the process for that deadline, completion can report `timedOut: true`.

For short commands, `output()` is enough. For large or long-running output, prefer `logs({ since, replay, follow })` and keep the latest **cursor** so a later request can resume the stream while the process is still in the current container. API details: [Processes API](https://developers.cloudflare.com/sandbox/1-0-preview/api/processes/).

## How long a process lives

A process lives only as long as it keeps running **in the current container** for that sandbox. After the container stops, old process IDs are not valid on a later container that serves the same sandbox ID.

### When the container stops

The container for a sandbox is not meant to run forever. After a period with nothing to do, Cloudflare may stop it. The container can also stop after failures, or when the platform replaces it during normal operations (for example after some deploys).

When that happens:

* Your app still uses the same sandbox ID (`user-123`).
* Processes that were running in the old container have exited. Their process IDs and live log buffers from that container are gone.
* The next time you use the sandbox for real work, Cloudflare may start a **new** container for the same sandbox ID. You start new processes there. You do not reconnect to process IDs from the previous container. Files from the old container are not still there unless your app restored them (for example from a backup or a mounted bucket).

Container stop and replace are not new in 1.0\. The preview makes process handles fail closed after the container that owned them is gone: the SDK does not retarget an old process ID at a new container for the same sandbox ID.

### What you see in the API

| What you try                                              | What happens                                                                                                 |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| The process is still running in the current container     | getProcess(id) returns it; you can read logs and wait as usual                                               |
| No container is running for the sandbox yet               | getProcess and listProcesses return null / \[\]. They do **not** start a container just to answer the lookup |
| You still hold a handle from before the container stopped | Calls on that handle fail with StaleProcessHandleError                                                       |
| You need the same _job_ after a stop                      | Start a new exec() from the launch and checkpoint your app stored                                            |

Recovery procedures: [Errors and recovery](https://developers.cloudflare.com/sandbox/1-0-preview/errors/).

### Keep the container running

While a process or terminal is active, the container can stay running so work continues across requests. When nothing is active, the container may stop again after idle time. Long-running product flows should either keep meaningful work active or rely on checkpoints and relaunch.

## Continue work across requests

Worker requests are short. Sandbox processes often are not. Design the job so a later request can either **resume the same process** or **start the job again**.

### What to store

| Always useful                                         | When you stream logs                        |
| ----------------------------------------------------- | ------------------------------------------- |
| Sandbox ID                                            | Latest log **cursor** from delivered events |
| Full exec argv                                        |                                             |
| cwd and env if the launch needs them                  |                                             |
| Application checkpoint (repo path, step, agent state) |                                             |

A process ID is a resume key for the **current** container only. It is not enough to restart the job after the container may have stopped.

### Case 1: The container still has the process

Use this path when the work is still running and the container has not been replaced — for example another request arrives seconds later while a build or server is up.

```js
const process = await sandbox.getProcess(storedProcessId);
if (process) {
	const stream = await process.logs({
		since: storedCursor,
		replay: true,
		follow: true,
	});
	// consume events; keep the latest cursor from each event
	return;
}
```

```ts
const process = await sandbox.getProcess(storedProcessId);
if (process) {
	const stream = await process.logs({
		since: storedCursor,
		replay: true,
		follow: true,
	});
	// consume events; keep the latest cursor from each event
	return;
}
```

You can also call `status()`, `waitForPort()`, `waitForExit()`, or `kill()` on that handle. Log cursors apply only while this process still exists in this container.

### Case 2: The process is gone — start from the stored job

Use this path when `getProcess` returns `null`, a call throws `StaleProcessHandleError`, or enough time has passed that the container may have stopped or been replaced.

```js
const process = await sandbox.exec(storedCommand, {
	cwd: storedCwd,
	env: storedEnv,
});
// persist process.id (and clear any old cursor)
await process.waitForPort(3000, { timeout: 60_000 });
```

```ts
const process = await sandbox.exec(storedCommand, {
	cwd: storedCwd,
	env: storedEnv,
});
// persist process.id (and clear any old cursor)
await process.waitForPort(3000, { timeout: 60_000 });
```

If the job also depends on files that lived only in the previous container, [back up and restore](https://developers.cloudflare.com/sandbox/guides/backup-restore/) those directories or mount durable storage before relying on the tree again. Backup and restore replace filesystem state. They do not bring back old process IDs or log buffers.

If the container is not ready yet, you may get `ContainerUnavailableError` — back off and run the same unit of work again. Refer to [Errors and recovery](https://developers.cloudflare.com/sandbox/1-0-preview/errors/).

### Choose a path

```js
async function continueJob(sandbox, job) {
	if (job.processId) {
		const existing = await sandbox.getProcess(job.processId);
		if (existing) {
			return existing; // Case 1 — same container, same process
		}
		// null: no container, or this ID is not in the current container
	}

	// Case 2 — relaunch from stored command and checkpoint
	const process = await sandbox.exec(job.command, {
		cwd: job.cwd,
		env: job.env,
	});
	job.processId = process.id;
	job.cursor = undefined;
	return process;
}
```

```ts
async function continueJob(sandbox: Sandbox, job: StoredJob) {
	if (job.processId) {
		const existing = await sandbox.getProcess(job.processId);
		if (existing) {
			return existing; // Case 1 — same container, same process
		}
		// null: no container, or this ID is not in the current container
	}

	// Case 2 — relaunch from stored command and checkpoint
	const process = await sandbox.exec(job.command, {
		cwd: job.cwd,
		env: job.env,
	});
	job.processId = process.id;
	job.cursor = undefined;
	return process;
}
```

If you still hold a handle object from before the container stopped, calls on that handle throw `StaleProcessHandleError`. Prefer `getProcess(id)` on each new request instead of reusing an old handle across requests.

## Processes and terminals

|        | Process (exec)             | Terminal                            |
| ------ | -------------------------- | ----------------------------------- |
| Role   | Supervised argv process    | Interactive PTY                     |
| Input  | Launch-time argv           | PTY input (write / browser connect) |
| Stop   | kill(signal?)              | interrupt() / terminate()           |
| Lookup | getProcess / listProcesses | getTerminal / listTerminals         |

Both follow the same [container lifetime rules](#how-long-a-process-lives). Terminal docs: [Terminals](https://developers.cloudflare.com/sandbox/1-0-preview/terminals/). API: [Terminals API](https://developers.cloudflare.com/sandbox/1-0-preview/api/terminals/).

## Logs and large output

`output()` buffers stdout and stderr and may set `truncated: true`. Prefer `logs()` when output may be large or the process runs longer than one Worker request.

```js
const stream = await process.logs({ follow: true, replay: true });
// each data/terminal event includes a cursor — store the latest
```

```ts
const stream = await process.logs({ follow: true, replay: true });
// each data/terminal event includes a cursor — store the latest
```

On a later request against the **same still-running container**, call `getProcess(id)` and resume with `logs({ since: cursor, replay: true, follow: true })`. After a new container starts for the sandbox, start a new process; the old cursor does not apply.

Event shapes, wait options, and readiness checks: [Processes API](https://developers.cloudflare.com/sandbox/1-0-preview/api/processes/).

## Related

* [Sandbox lifecycle](https://developers.cloudflare.com/sandbox/1-0-preview/lifecycle/)
* [Processes API](https://developers.cloudflare.com/sandbox/1-0-preview/api/processes/)
* [Errors and recovery](https://developers.cloudflare.com/sandbox/1-0-preview/errors/)
* [Errors API](https://developers.cloudflare.com/sandbox/1-0-preview/api/errors/)
* [Terminals](https://developers.cloudflare.com/sandbox/1-0-preview/terminals/)
* [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/)
* [Get started](https://developers.cloudflare.com/sandbox/1-0-preview/get-started/)
* [1.0 preview overview](https://developers.cloudflare.com/sandbox/1-0-preview/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/1-0-preview/processes/#page","headline":"Process execution · Cloudflare Sandbox SDK docs","description":"How the Sandbox SDK 1.0 preview runs commands — argv launches, process handles, and container lifetime.","url":"https://developers.cloudflare.com/sandbox/1-0-preview/processes/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-07","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```
