Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/commands/background.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,71 @@ for stdout, stderr, _ in command:
command.kill()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Agentic Security Review
Severity: HIGH

The JavaScript example interpolates JSON.stringify(prompt) directly into a shell command. JSON.stringify is not shell-safe, so command substitution payloads like `...` or $(...) can still execute when the command is parsed by the shell.

If this documented pattern is reused in an API/serverless handler with attacker-controlled prompt, it can lead to arbitrary command execution in the sandbox job context.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 1fc014a. Configure here.

```
</CodeGroup>

## Reconnect to a background command

A background command keeps running inside the sandbox even after the SDK disconnects. Because sandboxes are long-lived, you can start a long task in one process, return immediately, and reconnect from a completely different process later to wait for it and collect the result.

This is a common pattern for kicking off slow work (code generation, a build, a data job) from a serverless function or API handler: you start the command, store the sandbox ID and process ID, and let a later request pick the work back up.

The example below starts a long-running command (`run-codegen` here is a placeholder for your own binary or script) and returns the two identifiers you need to reconnect: the sandbox ID and the command's process ID (`pid`).

<CodeGroup>
```js JavaScript & TypeScript highlight={11}
import { Sandbox } from 'e2b'

// Start the task and return right away with the IDs needed to reconnect.
async function startGeneration(prompt: string) {
const sandbox = await Sandbox.create({ timeoutMs: 15 * 60_000 })

// Redirect output to a file so it survives after we disconnect.
const handle = await sandbox.commands.run(
`run-codegen ${JSON.stringify(prompt)} > /home/user/gen.log 2>&1`,
{ background: true, timeoutMs: 0 }
)

return { sandboxId: sandbox.sandboxId, pid: handle.pid }
}

// From a separate process, reconnect and wait for the result.
async function collectGeneration(sandboxId: string, pid: number) {
const sandbox = await Sandbox.connect(sandboxId)
const handle = await sandbox.commands.connect(pid)
await handle.wait()
return sandbox.files.read('/home/user/gen.log')
}
```
```python Python highlight={9}
import shlex
from e2b import Sandbox

# Start the task and return right away with the IDs needed to reconnect.
def start_generation(prompt: str):
sandbox = Sandbox.create(timeout=15 * 60)

# Redirect output to a file so it survives after we disconnect.
handle = sandbox.commands.run(
f"run-codegen {shlex.quote(prompt)} > /home/user/gen.log 2>&1",
background=True,
timeout=0,
)

return {"sandbox_id": sandbox.sandbox_id, "pid": handle.pid}


# From a separate process, reconnect and wait for the result.
def collect_generation(sandbox_id: str, pid: int):
sandbox = Sandbox.connect(sandbox_id)
handle = sandbox.commands.connect(pid)
handle.wait()
return sandbox.files.read("/home/user/gen.log")
```
</CodeGroup>

A few details make this reliable across processes:

- **Redirect output to a file.** The streamed `stdout`/`stderr` from a background command is only delivered to the process that started it. Writing to a file (`> /home/user/gen.log 2>&1`) gives you durable output you can read after reconnecting.
- **Disable the command timeout.** `timeoutMs: 0` (`timeout=0` in Python) removes the command's connection time limit so it isn't killed while running long. Set the sandbox `timeoutMs`/`timeout` high enough to outlast the whole job (15 minutes above). See [sandbox persistence](/docs/sandbox/persistence) if you need it to survive even longer.
- **Persist the IDs.** Return or store `sandboxId` and `pid` (for example in a database or the response of an API call). They are all a later process needs to call `Sandbox.connect()` and `commands.connect()`.

`commands.connect(pid)` reattaches to the running command, and `handle.wait()` blocks until it finishes. If you don't know the `pid`, you can look up running processes with `sandbox.commands.list()`.