Lifecycle states
Every sandbox passes through a series of states as it starts, runs, and shuts down. The SDK represents these asSandboxStatus values.
PENDING and CREATING are transient. The SDK polls through them automatically.
TERMINATING is a transient backend state that wait_until_complete() and stop().result()
drive through to a terminal state. RUNNING is the stable operational state.
COMPLETED and FAILED are terminal. UNSPECIFIED is mapped to COMPLETED by the SDK at
poll time.
Creation patterns
You can create a sandbox in two ways, differing in when the start RPC fires. The following sections describe each pattern and when to choose it.Sandbox.run(): immediate start
Sandbox.run() creates a sandbox and calls start().result() internally, blocking until the
backend accepts the request:
session.sandbox(): deferred start
session.sandbox() creates a sandbox object without making any network call. The start RPC
fires on first use:
Main command lifetime
The command passed torun() or session.sandbox() is the sandbox’s main process. When it
exits, the sandbox transitions to COMPLETED:
exec() on a long-running
sandbox rather than making the short command the main process.
Start a sandbox
After a sandbox object exists, you can start it explicitly or rely on the SDK to start it on first use. The following sections describe both approaches and how context managers interact with them.Explicit start
start() sends the start RPC and returns an OperationRef[None]:
Auto-start
Most operations auto-start the sandbox if it hasn’t been started yet. In the common case, you create a sandbox and start using it.Context managers and start
Context managers (with/async with) call start() on entry but do not call wait().
If you need the sandbox to be RUNNING before your first operation, call wait() explicitly:
exec() waits for RUNNING internally. Explicit
wait() is useful when you want to separate startup failures from operation failures.
Wait for a sandbox
The SDK provides two waiting methods depending on whether you need the sandbox to be ready for operations or to finish its main command. The following sections describe each method and how startup and operation timeouts relate.wait(): block until RUNNING
wait() polls until the sandbox reaches RUNNING, TERMINATING, or a terminal state:
wait() returns self for method chaining:
wait() handles it:
wait_until_complete(): block until terminal
wait_until_complete() blocks until the sandbox reaches a terminal state, polling through
TERMINATING automatically. Use this for sandboxes where the main command does the work:
raise_on_termination parameter controls whether wait_until_complete() raises
SandboxTerminatedError after this client called stop(). With the default
raise_on_termination=True, the SDK raises. External stops and lifetime-exceeded events
surface as COMPLETED without a distinct error, because the backend doesn’t yet provide
termination reason metadata.
Timeout phases
Startup wait time and operation timeouts are separate phases:- Startup wait: Time spent in PENDING/CREATING before reaching RUNNING. Controlled by the
timeoutparameter onwait()orwait_until_complete(). Typically 30 to 60 seconds depending on backend scheduling. - Operation timeout: Time for an individual exec, read, or write. Controlled by
timeout_secondsonexec(), orrequest_timeout_secondsinSandboxDefaults. Doesn’t include startup wait.
Operations and lifecycle
Operations likeexec(), read_file(), and write_file() auto-start the sandbox if needed,
then wait for RUNNING before proceeding:
timeout_seconds) applies only after the sandbox is RUNNING. Startup
time is not counted against it.
Stop a sandbox and end of life
This section covers how to shut down a sandbox, what happens after a stop, and when to usestop() versus delete().
stop()
stop() sends a stop request and returns OperationRef[None]. The sandbox transitions through
TERMINATING (grace period draining) before reaching a terminal state. The returned
OperationRef resolves when the backend confirms the terminal state, not only when the stop
RPC succeeds:
stop() handles in-flight starts: if a start is still being processed, it waits for start to
complete before stopping. Concurrent or repeated calls to stop() share one stop operation and
don’t issue duplicate stop RPCs. This makes repeated stop() calls safe and cheap.
Post-stop behavior
Afterstop() is called, the sandbox transitions through TERMINATING (the grace period
draining state) and then reaches a terminal state (COMPLETED or FAILED). After stop() has
been called, the sandbox is unusable. Further operations raise SandboxNotRunningError:
status property is cached from the last API call. For fresh data before stopping, use
get_status():
Context manager exit
Context managers callstop() automatically on exit:
stop() compared to delete()
Use
stop() for sandboxes you’re actively using. Use delete() for cleanup of sandboxes
discovered through Sandbox.list() or Sandbox.from_id():
Under the hood
The SDK runs all gRPC operations on a background daemon thread with its own asyncio event loop. This design means:- The sync API (
.result()) blocks the calling thread while the background loop handles the network call. - The async API (
await) bridges to the same background loop, so both patterns use the same underlying implementation. - The background loop starts lazily on first use. gRPC channels are also created lazily.
- Auto-start works by checking if
sandbox_idis None before each operation and triggeringstart()if so. - On process exit, cleanup handlers (atexit + signal handlers) stop all sandboxes in registered sessions. A second Ctrl+C during cleanup forces immediate exit.
nest_asyncio. See the Sync compared to async patterns guide for usage patterns.