# Timeouts

Three separate clocks apply to your code: how long the sandbox lives, how long
a single command may run, and how long one API request may take.

## The sandbox timeout

Every sandbox has a deadline. It is set when you create the sandbox and
defaults to five minutes — `300_000` ms in TypeScript, `300` seconds in
Python. When the deadline passes the sandbox is killed unless you asked for
something else.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', { timeoutMs: 1_800_000 })

console.log(sandbox.sandboxId)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base", timeout=1800)

print(sandbox.sandbox_id)
```

</Tab>
</CodeTabs>

TypeScript counts milliseconds and Python counts seconds. The API takes whole
seconds, so the TypeScript SDK rounds a millisecond value up to the next
second before sending it.

<Callout kind="note">
`getInfo` returns `endAt` as a fixed instant. An idle sandbox expires on the
same schedule as one running a build.
</Callout>

## Extend it while running

`setTimeout` replaces the remaining time with a new one, measured from the
moment the call arrives. It can extend or shorten the life of the sandbox, and
each call replaces the previous deadline. There is an instance method and a
static one that takes a sandbox ID, so a process that never held the sandbox
object can still keep it alive.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

// Another fifteen minutes from now.
await sandbox.setTimeout(900_000)

// Or from anywhere, with only the ID.
await Sandbox.setTimeout(sandbox.sandboxId, 900_000)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

# Another fifteen minutes from now.
sandbox.set_timeout(900)

# Or from anywhere, with only the ID.
Sandbox.set_timeout(sandbox.sandbox_id, 900)
```

</Tab>
</CodeTabs>

Because each call restarts the count from the current time, a long job is
usually a short timeout plus a heartbeat, not one enormous timeout.

To read the deadline rather than set it, ask the sandbox for its information.
`getInfo` returns `endAt` in TypeScript and `get_info` returns `end_at` in
Python, both as a date.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')
const info = await sandbox.getInfo()

console.log(info.startedAt, info.endAt)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")
info = sandbox.get_info()

print(info.started_at, info.end_at)
```

</Tab>
</CodeTabs>

`connect` also takes a timeout, but it only ever extends: reconnecting to a
running sandbox with a shorter timeout than the one it already has leaves the
existing deadline alone. Use `setTimeout` when you mean to shorten it.

## What happens at timeout

By default the sandbox is killed and its filesystem goes with it. Pass
`lifecycle` at create time to have it pause instead, so you can resume it
later from the same sandbox ID.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', {
  timeoutMs: 600_000,
  lifecycle: { onTimeout: { action: 'pause', keepMemory: true } },
})

console.log(sandbox.sandboxId)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create(
    "base",
    timeout=600,
    lifecycle={"on_timeout": {"action": "pause", "keep_memory": True}},
)

print(sandbox.sandbox_id)
```

</Tab>
</CodeTabs>

`onTimeout` (`on_timeout` in Python) also accepts the bare string `'pause'` or
`'kill'`. The object form exists to carry `keepMemory` (`keep_memory`), which
chooses the kind of snapshot the automatic pause takes:

- `true` keeps memory as well as the filesystem, so running processes and
  open connections are still there when you resume. TypeScript sends `true`
  when you omit it; Python sends nothing and the API chooses.
- `false` keeps only the filesystem. Resuming cold-boots the sandbox from
  disk, and anything that was running is gone.

`keepMemory` is only meaningful with `action: 'pause'`. Passing it alongside
`action: 'kill'` is a type error in both SDKs, and at runtime it raises
`InvalidArgumentError` in TypeScript and `InvalidArgumentException` in Python.

A sandbox that has sat paused for 14 days is deleted. Pausing it again
restarts the 14 days. See
[Pause, resume and snapshots](/docs/sandbox/pause-and-snapshots).

Once a sandbox has reached the end of its life, a call **into** it — a
command, a file read, a PTY write — fails with `TimeoutError`
(`TimeoutException`). The message tells you to raise `timeoutMs` (`timeout`)
at create time, or to call `setTimeout` (`set_timeout`). A control-plane call
for a sandbox that no longer exists — `getInfo`, `setTimeout`, `connect` —
raises `SandboxNotFoundError` (`SandboxNotFoundException`) instead. See
[Errors](/docs/errors).

## Command and request timeouts

The other two clocks are shorter and are set per call, not per sandbox.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Clock</th><th>TypeScript</th><th>Python</th><th>Default</th></tr>
  </thead>
  <tbody>
    <tr><td>Sandbox life</td><td>`timeoutMs` on `create`</td><td>`timeout` on `create`</td><td>`300_000` ms / `300` s</td></tr>
    <tr><td>One command</td><td>`timeoutMs` on `commands.run`</td><td>`timeout` on `commands.run`</td><td>`60_000` ms / `60` s</td></tr>
    <tr><td>One API request</td><td>`requestTimeoutMs`</td><td>`request_timeout`</td><td>`60_000` ms / `60` s</td></tr>
  </tbody>
</table>
</div>

A command that runs past its own timeout raises `TimeoutError` /
`TimeoutException` even though the sandbox is healthy and still running. Raise
the command timeout, or start the command in the background and wait for it
yourself. Passing `0` — `timeoutMs: 0` in TypeScript, `timeout=0` in Python —
removes the limit on a command entirely.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', { timeoutMs: 900_000 })

const result = await sandbox.commands.run('sleep 120 && echo done', {
  timeoutMs: 300_000,
})

console.log(result.exitCode, result.stdout)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base", timeout=900)

result = sandbox.commands.run("sleep 120 && echo done", timeout=300)

print(result.exit_code, result.stdout)
```

</Tab>
</CodeTabs>

<Callout kind="warning">
A command timeout never extends the sandbox. If the sandbox deadline passes
first, the sandbox stops mid-command — killed, or paused if you set
`onTimeout: 'pause'`.
</Callout>

The error you catch then describes the sandbox, not the command. The SDK sees
the connection drop mid-request and checks whether the sandbox is still alive.
When it is gone, you get `TimeoutError` (`TimeoutException`) saying the sandbox
was killed or reached its end of life while the request was in flight. If that
check cannot reach the sandbox, Python raises the underlying transport error
unchanged and TypeScript wraps it in `SandboxError` with the transport message.

`requestTimeoutMs` (`request_timeout`) bounds a single non-streaming request.
That covers the API — creating a sandbox, listing sandboxes, setting a timeout
— and the sandbox itself: reading or writing a file, listing or killing a
command. Pass it on `create` and `connect` to set it for every later call on
that sandbox, or on an individual call to override it there. Setting it to `0`
disables it.

It does not bound a streaming call. On `commands.run` and the filesystem
watchers, TypeScript uses it only for the handshake and Python ignores it
entirely — bound those with the command timeout instead.

## Maximum length by plan

The longest deadline you can ask for depends on your plan.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Plan</th><th>Maximum sandbox length</th></tr>
  </thead>
  <tbody>
    <tr><td>Micro</td><td>1 hour</td></tr>
    <tr><td>Base</td><td>12 hours</td></tr>
    <tr><td>Scale</td><td>24 hours</td></tr>
  </tbody>
</table>
</div>

Keep every `timeoutMs` and `setTimeout` value inside your plan's maximum.
Neither SDK checks the number before sending it. If the API refuses the
request, `create` and `setTimeout` raise `SandboxError` (`SandboxException`)
carrying the status code and the server's own sentence — the fallback described
in [Errors](/docs/errors). Read `endAt` back from `getInfo` when the exact
deadline matters. See [Limits and pricing](/docs/limits-and-pricing).

## Async Python

`AsyncSandbox` has the same calls awaited: `await AsyncSandbox.create("base", timeout=1800)`,
`await sandbox.set_timeout(900)` and `await sandbox.get_info()`. It takes the
same `lifecycle` argument.

Next: [Errors](/docs/errors).
