Skip to main content
A sandbox create normally waits until the sandbox is running. When the fleet has room that takes a few seconds. When it does not, a new host has to boot first, which can take several minutes on bare-metal hosts, and a call that waits for it can outlive its own timeout. Instead of waiting on the create call, create the sandbox, poll for it when convenient, and set a limit on how long it may wait for capacity. Use this when you create many sandboxes at once.

Create without waiting

Pass wait=False and the create returns as soon as the sandbox is durable, with the sandbox in the pending state. The sandbox is scheduled and started exactly as a normal create; only the waiting moves to you.
The handle has two methods. ready() polls the sandbox every two seconds until it is running and returns a connected Sandbox. status() fetches the current state once. A sandbox known only by id, in another process for example, is picked up with Sandbox.connect(sandbox_id), which also waits while the sandbox is pending.

Why a sandbox is pending

While a sandbox waits, its pending_reason says why. Read it from status(), from GET /sandboxes/{id}, or from a SandboxPending error.

Limit the queue time

By default a pending sandbox waits until there is room, however long that takes. If you would rather give up at some point, set max_pending_secs on the create. When the bound runs out while the sandbox is still waiting for capacity, the server terminates it with termination_reason: no_capacity, and the last pending_reason explains what it was waiting for. 0 fails at once if the sandbox cannot be placed right away.
Bare-metal hosts take up to 20 minutes to boot. A bound shorter than that expires the sandbox just as the host it triggered comes up, and the next burst pays for a boot again. Use 30 minutes or more for capacity waits.
The bound applies only while the sandbox waits for capacity. Once it is placed and booting, the platform’s own startup limits apply. A named sandbox whose resume cannot find capacity within the bound returns to suspended rather than terminating, so nothing it holds is lost.

Timeouts

The blocking Sandbox.create() and pending.ready(timeout=...) raise SandboxPending when their wait runs out. The sandbox is not deleted. It keeps its place in the queue and starts when there is room. The error carries sandbox_id and the last pending_reason, so you can wait again, connect from elsewhere, or terminate it.
If you want the old behaviour, where a timed-out create deletes the sandbox, pass cancel_on_timeout=True (cancelOnTimeout: true). Deleting a pending sandbox yourself terminates it with termination_reason: cancelled.
Before SDK versions with wait, a create that timed out deleted its sandbox. A retry loop written for that behaviour (“create failed, create again”) now accumulates pending sandboxes until they run or are cancelled. Reuse the id from the error instead, or set cancel_on_timeout.

Retries

A create with wait=False answers in well under a second, so a lost response is rare, but a retry of an unanswered create can still make a second sandbox. When that matters, give the sandbox a name. Names are unique per namespace: a repeated create of the same name is an HTTP 409, and get_or_create resolves it to the existing sandbox. Named sandboxes suspend rather than terminate when their timeout_secs elapses, so terminate them explicitly when the work is done.

Create many sandboxes at once

For a batch, create everything up front with wait=False, then poll the list endpoint. One list call sees every sandbox in the namespace, so ten thousand sandboxes cost one request per poll, not ten thousand.
Sandboxes start oldest first as hosts come up, so a batch that waited for a host is not overtaken by creates that arrived after it. Sandboxes still waiting when their bound runs out fail with no_capacity and drop out of the list. Webhook subscribers can replace the poll with the sandbox.running and sandbox.failed events.

Learn more