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.
import { Sandbox } from '@impello/sdk'
const sandbox = await Sandbox.create('base', { timeoutMs: 1_800_000 })
console.log(sandbox.sandboxId)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.
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.
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)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.
import { Sandbox } from '@impello/sdk'
const sandbox = await Sandbox.create('base')
const info = await sandbox.getInfo()
console.log(info.startedAt, info.endAt)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.
import { Sandbox } from '@impello/sdk'
const sandbox = await Sandbox.create('base', {
timeoutMs: 600_000,
lifecycle: { onTimeout: { action: 'pause', keepMemory: true } },
})
console.log(sandbox.sandboxId)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:
truekeeps memory as well as the filesystem, so running processes and open connections are still there when you resume. TypeScript sendstruewhen you omit it; Python sends nothing and the API chooses.falsekeeps 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.
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.
Command and request timeouts
The other two clocks are shorter and are set per call, not per sandbox.
| Clock | TypeScript | Python | Default |
|---|---|---|---|
| Sandbox life | timeoutMs on create | timeout on create | 300_000 ms / 300 s |
| One command | timeoutMs on commands.run | timeout on commands.run | 60_000 ms / 60 s |
| One API request | requestTimeoutMs | request_timeout | 60_000 ms / 60 s |
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.
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)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.
| Plan | Maximum sandbox length |
|---|---|
| Micro | 1 hour |
| Base | 12 hours |
| Scale | 24 hours |
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. Read endAt back from getInfo when the exact
deadline matters. See 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.