# Long-running tasks

> Keep work going for hours or days — idle auto-pause and wake on use, auto-stop, extending, background commands, live attach, command history and systemd services.

Sandboxes are built to stay up. Two things stop one on its own, and you control both: **idle auto-pause**, which pauses a sandbox nobody is using and wakes it on the next use, and **auto-stop**, a hard deadline.

## Idle auto-pause and wake on use

A sandbox pauses itself after `idleStopMinutes` without activity. Pausing is a stop **with memory**: files and running processes are kept, and nothing is billed for compute while it's paused. Its disk and memory image still count toward your plan's [included storage](/docs/billing#storage), like any stopped sandbox. The next use wakes it.

| Plan | Default `idleStopMinutes` |
| - | - |
| $20 / month | 30 |
| $100 and $500 / month | 60 |
| $2,000 / month | 120 |

- `0` turns it off; the maximum is `10080` (7 days). Auto-stop (below) stays the hard limit either way.
- Forks copy their source's setting. Sandboxes created before October 26, 2026 have it off until you set it.
- With snapshots off it's always `0` (a stop would discard the sandbox), and asking for more answers `400 invalid_idle_stop`.
- It's off for accounts with [zero data retention](/docs/security), where a stop deletes the sandbox: turning zero data retention on sets every sandbox to `0`.

```bash title="CLI"
sbx new --idle 60                 # pause after an hour without activity
sbx idle sb_k3v9x2mq 0            # never pause this one
```

```bash title="curl"
curl -sS -X PATCH https://sandboxer.com/api/v1/sandboxes/sb_k3v9x2mq \
  -H "Authorization: Bearer $SANDBOXER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"idleStopMinutes": 60}'
```

It's also `idleStopMinutes` on `POST /sandboxes` and on the MCP tools `create_sandbox` and `extend_sandbox`. Every sandbox object carries `idleStopMinutes` and `lastActivityAt`.

**What counts as activity:** any API call that reaches the VM (commands, files, processes, the terminal, `sbx ssh`, the desktop, agent prompts), requests to its [hosted URLs](/docs/hosting), the end of a command or agent turn, and every start or resume.

**It stays awake while** a terminal or SSH websocket is open, a command is running (detached ones too), an agent turn runs, someone is logged in over SSH, the 1-minute load average is at least 0.5, or the file `~/.sandboxer/keep-awake` exists. Create that file for work our API can't see, such as a daemon streaming from a model:

```bash
touch ~/.sandboxer/keep-awake     # rm it to allow pausing again
```

**Wake on use.** A sandbox paused for being idle resumes by itself:

- the next API call that needs it waits for it (up to 60 seconds);
- the next request to one of its hosted ports is held while it wakes (up to 45 seconds, then `503` with `Retry-After`). A private port wakes only for a request that carries its token (`?_token=`, its cookie or header); an expiring signed link (a [desktop](/docs/desktop) URL, or a [Daytona-API](/docs/daytona) signed preview URL) doesn't wake it, so resume it through the API first;
- raw SSH to its port doesn't wake it, but `sbx ssh` does.

A sandbox stopped by you, by auto-stop or by billing doesn't wake by itself: resume it.

## Auto-stop

Every sandbox has a time-to-live. When it runs out we stop the sandbox (memory and disk are kept; billing stops). The deadline is `archiveAfter` on the sandbox.

| Setting | CLI | API |
| - | - | - |
| Default: 1 hour | `sbx new` | `{}` (or `"ttlSeconds": 3600`) |
| Custom | `sbx new --ttl 28800` | `"ttlSeconds": 28800` |
| Never | `sbx new --no-auto-stop` | `"ttlSeconds": null` |
| Maximum | 30 days | `2592000` |

Extend a running sandbox at any time. The new deadline is **now + TTL**:

```bash title="CLI"
sbx extend sb_k3v9x2mq --hours 12
sbx extend sb_k3v9x2mq --ttl 2592000
sbx extend sb_k3v9x2mq --no-auto-stop
```

```bash title="curl"
curl -sS -X PATCH https://sandboxer.com/api/v1/sandboxes/sb_k3v9x2mq \
  -H "Authorization: Bearer $SANDBOXER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds": 43200}'
```

The dashboard shows a countdown ("Stops in") on every sandbox with an **Extend** button.

> **Note** Because a stop keeps memory, auto-stop is cheap insurance: if the deadline hits while your job is still running, `sbx resume` brings the job back exactly where it was.

## Synchronous commands

```bash
sbx exec sb_k3v9x2mq "npm test"                       # exits with the remote exit code
sbx exec sb_k3v9x2mq --cwd my-app --timeout 120 -- npm run build
```

```json
POST /sandboxes/sb_k3v9x2mq/commands
{ "command": "npm test", "cwd": "my-app", "timeoutSeconds": 120 }
```

Synchronous commands time out after at most 600 seconds. For anything longer, detach.

## Stream output

Pass `"stream": true` to receive newline-delimited JSON as the command writes:

```bash
curl -N -sS -X POST https://sandboxer.com/api/v1/sandboxes/sb_k3v9x2mq/commands \
  -H "Authorization: Bearer $SANDBOXER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"npm test","stream":true}'
```

```json
{"type":"started","commandId":"cmd_k3v9x2mq7tbw"}
{"type":"stdout","data":"✓ api.test.ts (4 tests)\n"}
{"type":"stderr","data":"warning: …\n"}
{"type":"exit","exitCode":0,"success":true,"timedOut":false}
```

## Background (detached) commands

```bash
pid=$(sbx exec sb_k3v9x2mq --detach "npm run build" | jq -r .processId)
sbx exec sb_k3v9x2mq --status "$pid"     # running? exit code? last lines of output
sbx exec sb_k3v9x2mq --attach "$pid"     # follow live output until it ends
sbx exec sb_k3v9x2mq --kill "$pid"       # stop it and its children
```

- Output goes to `~/.sandboxer/processes/<pid>.log` (and `.err.log`) on the sandbox.
- `GET /commands/{processId}` returns status plus the tail of the logs; pass `stdoutOffset`/`stderrOffset` to read incrementally.
- `GET /commands/{processId}/stream` attaches over NDJSON from any offset. The stream ends with `exit`, or with `reconnect` after 10 minutes — open it again with the offsets it gives you and nothing is lost or repeated.
- A detached process survives a stop and resume (memory is restored). If a sandbox cold-boots instead, its status becomes `lost`; the log files remain.

## Command history

Every command run through the API is recorded with its output, readable even while the sandbox is stopped:

```bash
sbx history sb_k3v9x2mq                         # newest first
sbx history sb_k3v9x2mq cmd_k3v9x2mq7tbw        # stored stdout
sbx history sb_k3v9x2mq cmd_k3v9x2mq7tbw --stderr --follow
sbx ps sb_k3v9x2mq                              # live process list, busiest first
```

| Kept | For |
| - | - |
| Commands run via `exec` / the API | 90 days |
| Output of other processes in the sandbox | 72 hours |

Create a sandbox with `--no-monitoring` to keep none of this (live output still works).

## Services that should always run

For anything that must survive cold boots and forks, make it a systemd service:

```bash
sudo tee /etc/systemd/system/app.service >/dev/null <<'EOF'
[Unit]
Description=My app
After=network-online.target

[Service]
User=user
WorkingDirectory=/home/user/my-app
ExecStart=/usr/bin/npm run start
Restart=always
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now app
host 3000
```

Docker containers with `--restart unless-stopped` behave the same way.

## Setup scripts

Pass a script at create time; it runs in the background as `user` once the sandbox is ready, and never delays `ready`:

```bash
sbx new --setup-file ./setup.sh
```

```json
{ "setupScript": "#!/bin/bash\nset -e\ncd ~ && git clone https://github.com/acme/app && cd app && npm ci" }
```

Watch `setupStatus` (`pending`, `running`, `done`, `failed`) and `setupError` on the sandbox. Combine with `--from <template>` so the heavy installs are already in the template and the script only does per-sandbox work.

## Patterns

- **Agent worker:** create with `--no-auto-stop`, run the agent loop as a systemd service, stop the sandbox when the queue is empty, resume when work arrives.
- **Nightly job:** `sbx new --from bench-template --ttl 7200`, run the job detached, let auto-stop clean up.
- **Long build with a deadline:** `sbx exec --detach`, then `sbx exec --attach` from CI so the job log shows live output.
