Manage Sandboxes
A sandbox is the OpenShell data plane: a safe, private execution environment where an AI agent runs. Each sandbox combines runtime isolation with OpenShell policy controls that prevent unauthorized data access, credential exposure, and network exfiltration.
Create a Sandbox
Create a sandbox with a single command. For example, to create a sandbox with Claude, run:
The trailing command is the sandbox’s canonical main process. OpenShell streams
its output and returns its exit status. By default, exit code 0 leaves a
retained sandbox in Completed; a nonzero exit leaves it in Error with a
MainProcessFailed condition. With no trailing command, OpenShell starts a
login shell in a retained pseudo-terminal: /bin/bash -l when the image
provides bash, otherwise a shell detected in the image such as /bin/sh on
minimal bases like Alpine. Add --detach to create the sandbox without
attaching:
Use --restart-policy on-failure to replace the runtime after a nonzero main
process exit, or --restart-policy always to replace it after every exit:
The default policy is never. The gateway starts the first replacement as soon
as terminal output has been delivered. Repeated quick exits wait 1 second, then
double the delay up to 3 minutes. Running for 10 seconds resets the delay. A
replacement starts a new supervisor and main process. It retains the sandbox
identity and configuration; filesystem persistence follows the compute driver’s stop and
start behavior. It does not restore process memory or terminal history.
Detached commands have no attachment grace period. When the command exits, OpenShell records its terminal phase immediately. For a foreground command, the create request declares one expected SSH attachment. OpenShell retains the terminal transport until that connection drains and closes naturally, then finalizes ephemeral cleanup.
Use --no-keep for an ephemeral command. OpenShell drains stdout and stderr,
captures the command result, and deletes the sandbox after the command exits:
Combine --detach and --no-keep for a background workload whose lifecycle
belongs entirely to the gateway:
The create command returns after the workload is ready. The gateway keeps the canonical process running without a host-side attachment and deletes the sandbox after that process exits.
--upload cannot yet be combined with a trailing main command because uploads
finish after the canonical process starts. Create a scratch sandbox, upload the
files, then launch the workload with sandbox exec, or build the files into the
sandbox image.
For automation, use --output json or --output yaml to get machine-readable sandbox metadata after creation:
Every sandbox requires a gateway. Register or select one before running sandbox commands:
CPU and Memory
Set per-sandbox CPU and memory amounts with --cpu and --memory:
CPU values use Kubernetes-style quantities such as 500m, 1, or 2.5.
Memory values use byte quantities such as 512Mi, 4Gi, or 8G.
Docker and Podman apply these values as runtime limits. Kubernetes applies each value as both the request and the limit so the scheduler reserves the same amount the sandbox can use. The VM driver currently accepts these flags but does not change VM allocation.
Driver-Specific Configuration
Pass experimental driver-owned settings with --driver-config-json. The value
must be a JSON object keyed by driver name. The gateway forwards only the block
for its configured compute driver:
Nested keys inside each driver block use snake_case. The top-level envelope keys
are driver names, such as kubernetes, and are not part of the nested schema.
Use this only for driver-specific fields that do not have a stable CLI flag.
Prefer stable flags such as --cpu, --memory, and --gpu when they cover
the same behavior.
GPU Resources
To request GPU resources, add --gpu:
Request a specific number of GPUs by passing a count to --gpu:
When you omit the count, OpenShell treats the request as --gpu 1.
Kubernetes honors counted --gpu requests by setting the nvidia.com/gpu
limit. Docker and Podman select the requested number of default NVIDIA CDI
devices in round-robin order. VM gateways accept only one GPU, either through
--gpu or --gpu 1; a single gpu_device_ids entry works with either form.
For Docker-backed sandboxes, GPU injection uses Docker CDI. If you enable Docker CDI after the gateway starts, restart the gateway so OpenShell can detect the updated Docker daemon capability.
Docker and Podman refresh the CDI inventory before validating or creating a
default GPU request, so CDI devices added or removed after the driver starts can
be reflected in later sandbox creates. On WSL2 all-only runtimes, the default
can fall back to nvidia.com/gpu=all; that fallback counts as one selectable
device.
Exact GPU device selection is driver-specific and still requires --gpu. For
Docker or Podman, pass CDI IDs through cdi_devices. The top-level key must
match the active driver; replace docker with podman when using Podman. CDI
IDs must be valid CDI qualified names (<vendor>/<class>=<name>); host-device
paths are rejected. Explicit selectors are not restricted to NVIDIA names.
The list must not contain duplicate IDs, and
its length must match the effective GPU count:
Sandbox Images
Without --from, the gateway uses its configured default workload image. The
built-in default is nvcr.io/nvidia/base/ubuntu:24.04.
Use --from with an explicit OCI image reference or a rootfs tar archive:
--from does not expand catalog aliases and does not build local Dockerfiles or
directories. Build and tag the image with the container engine used by your
local gateway, then pass the resulting image reference:
For a remote gateway, push the image to a registry that the gateway can pull from and use that registry image reference.
Pre-0.1.0 breaking change: openshell sandbox create --from ./Dockerfile
and directory sources no longer build images. Bare catalog names are no longer
expanded. Build or select an image and pass its explicit reference.
Rootfs Tar Archives
A rootfs tar archive (.tar, .tar.gz, .tgz) is a flat filesystem produced
by docker export, podman export, or buildah mount plus tar. It lets you
create a sandbox without a registry or a running image daemon:
Rootfs tar sources require a local gateway running the VM compute driver. The CLI asks the gateway for a staging slot, writes the archive to the location the gateway allocates, and passes back a single-use token; the gateway resolves that token to a path for the driver. Because the CLI writes the archive directly to the gateway host’s filesystem, the two must share a filesystem and run as the same user. Gateways using the Docker, Podman, or Kubernetes drivers reject rootfs tar sources.
Gzip-compressed archives (.tar.gz, .tgz) are decompressed while the gateway
stages them, so the sandbox sees the same filesystem either way. Compression is
detected from the archive contents, not the file name.
The gateway caps archive size (10 GiB by default, configurable with the VM
driver’s rootfs_tar_max_bytes), and reclaims an unused staging slot after 30
minutes. The cap applies to the expanded archive too: a compressed source that
decompresses past the limit is rejected.
To reuse image and resource settings across sandboxes, see Templates.
Default Workload Image
OpenShell defaults to nvcr.io/nvidia/base/ubuntu:24.04 unless the gateway
operator configures another image. The image provides a minimal Ubuntu Noble
userspace. It does not include agent CLIs or an image-baked OpenShell policy.
OpenShell applies its built-in restrictive policy when no explicit policy is
provided.
Create a sandbox with the default image:
Override it with any image visible to the active compute driver:
Refer to Default Policy, Run Your First Agent, and the bring-your-own-container example.
Connect to a Sandbox
Attach to the canonical main process in a running sandbox:
Press Ctrl-D or Ctrl-P, then Ctrl-Q in sequence to disconnect. The main
process keeps running and its stdin stays open. A later connect attaches to
the same process instance and replays up to 1 MiB of recent output. One
attachment owns stdin at a time. Use sandbox exec --tty -- /bin/bash -l when
you want a new independent shell instead.
If an established connection is interrupted, for example when a laptop sleeps and wakes, the CLI obtains a new SSH session and reattaches to the same main process. It retries transient transport failures for up to 60 seconds. Initial authentication failures, sandbox lifecycle changes, and clean SSH exits are not retried.
The supervisor sends SSH keepalive probes after 15 seconds without inbound traffic and closes a connection after 60 seconds without receiving peer bytes, including when relay writes are stalled. Healthy idle clients answer these probes and stay attached. The timeout starts when the supervisor last reads peer bytes; relay buffering and scheduling can delay detection after physical network loss. The canonical process keeps running when its dead attachment closes.
If recovery reaches the sandbox before the old connection releases stdin, the replacement reports attached read-only. After the old connection times out, type again: the replacement acquires stdin if it is available, reports input enabled, and forwards that new input. Keystrokes sent while another attachment owned stdin are discarded. An explicitly read-only attachment stays read-only, and recovery never displaces a healthy input owner.
Ctrl-C retains its normal terminal behavior and interrupts the foreground
process. For read-only attachments, Ctrl-C or Ctrl-D exits only the current
viewer.
OpenSSH’s ~. escape reports the same status as a broken transport, so it
starts automatic recovery instead of exiting. After ~., use Ctrl-D or
Ctrl-P, then Ctrl-Q once OpenShell reattaches, or press Ctrl-C while the CLI
is between retry attempts to cancel recovery.
Launch VS Code or Cursor directly into the sandbox workspace:
When --editor is used, OpenShell keeps the sandbox alive and installs an
OpenShell-managed SSH include file instead of cluttering your main
~/.ssh/config with generated host blocks.
Execute a Command in a Sandbox
For raw ExecSandboxInteractive clients, ending the request stream closes stdin
and terminal resize input. Continue reading the response to drain stdout/stderr
and receive the exit event and final gRPC status. Input EOF does not immediately
close the SSH output channel and is distinct from cancelling the RPC. With a PTY,
input closure is not equivalent to sending a terminal Ctrl-D keystroke.
The supervisor limits pending stdin to 4 MiB per exec channel. If input arrives faster than the command consumes it and this buffer fills, further input fails the exec with exit code 74. The supervisor logs process stdin buffer is full. This also applies to SSH file transfers that use exec channels. The limit counts input waiting to be written, so clients that support larger transfers can continue as the command consumes data. Input EOF drains bytes already accepted; cancelling the exec discards pending input.
Run a one-shot command inside a running sandbox without opening an interactive shell:
Pipe stdin into the command:
The CLI sends small piped input in one request for compatibility with older
gateways. It streams larger input in bounded frames, including for commands
without a TTY, so input is not limited by the gateway’s per-message request
size. Piped input is limited to 4 MiB; use sandbox upload for larger files.
The CLI waits at most 200 ms for piped stdin to reach EOF. If the pipe is
still open after that, for example under a CI runner or a harness that never
closes stdin, the command starts anyway and the remaining input streams to it
as it arrives. The CLI closes remote stdin when the pipe reaches EOF and
continues reading command output until the command finishes. Redirect stdin
from /dev/null when a command needs no input.
If streamed stdin exceeds the 4 MiB limit, the CLI reports an error and warns that the command may already have processed partial input. Cancellation does not undo that work.
The command’s exit code is propagated to the CLI, so exec works in scripts that check return codes.
Large stdout and stderr streams are delivered before a successful exit. A slow
reader backpressures the command. If output delivery fails, exec returns a
failure instead of reporting success with incomplete output.
If a background process keeps stdout or stderr open for more than 30 seconds
after the command exits, exec reports an output delivery failure.
A failed final gRPC status also makes the CLI report failure, even if the remote command reported exit code zero. The CLI does not automatically retry the execution.
Run an interactive shell with a TTY:
Run exit or press Ctrl-D to close this separate shell.
OpenShell allocates a TTY automatically when both stdin and stdout are terminals. Force the behavior with --tty or disable it with --no-tty.
Skip shell startup files
By default sandbox exec runs the command through a login shell (bash -lc), so the sandbox user’s first available .bash_profile, .bash_login, or .profile is sourced first (and .bashrc only if that login file sources it). This makes tool-specific environment configuration available automatically, which suits interactive and tool-discovery use.
For automation and managed checks that need predictable output and side effects, pass --no-login-shell so those startup files are not sourced before the command runs:
In this mode a sandbox user’s login startup files cannot write to the command’s output, create files, or otherwise affect the requested command before it starts. The command still runs under bash -c, which reads BASH_ENV if it is set in the command’s environment. The default (login-shell) behavior is unchanged when the flag is omitted.
Set Environment Variables
Inject environment variables into the sandbox at creation time:
Variables set with --env are available to all processes in the sandbox, including the initial command, interactive shells, and exec commands.
When an --env key looks like a credential — a known provider variable, or a name whose underscore-separated segments include a credential word such as TOKEN, SECRET, PASSWORD, CREDENTIAL, API_KEY, ACCESS_KEY, or SECRET_KEY (for example DB_TOKEN or MY_ACCESS_KEY) — sandbox create prints a non-blocking warning. Matching is on whole segments, so unrelated names like TOKENIZERS_PARALLELISM or PASSWORDLESS_LOGIN do not warn. The agent inside the sandbox can read plain environment values directly, so to hide a secret from the agent, attach it through a profile-backed provider with --provider instead. Suppress the warning with --no-credential-warnings. Detection uses the key name only; values are never inspected or printed.
You can also set per-command environment variables with sandbox exec:
Per-command variables override the sandbox-level environment for that command only.
Environment variable names starting with OPENSHELL_ are reserved. Keys must match [A-Za-z_][A-Za-z0-9_]*.
Label a Sandbox
Attach labels when you create a sandbox to track ownership, environment, or workflow grouping:
List only the sandboxes that match a label selector:
The Python SDK accepts the same gateway labels and selectors. Labels passed to
create are stored on the gateway sandbox object and are returned on
SandboxRef.labels, so selector-based listing finds Python-created sandboxes:
Python SDK list methods return a lazy Pager whose iteration yields one
Page per gateway request. Use list_all only when you want to exhaust the
collection; pass page_token to resume from a token saved from an earlier page.
Create reusable sandbox templates through the Python SDK when several runs should share the same workload shape:
For non-interactive automation, pass a renewable client-credentials provider. Omitted issuer, client ID, audience, and scopes are read from the active gateway’s metadata. The client requires TLS for non-loopback gateways:
Expose Long Running Services
Service forwarding makes a long-running process inside a sandbox reachable through a gateway-managed URL. Use it for development servers, notebooks, dashboards, or other services that keep listening after the sandbox starts. Run the service on loopback inside the sandbox, expose its port, then open the URL printed by OpenShell.
Expose the unnamed service as part of sandbox creation when the sandbox’s main process starts the server:
The CLI includes the unnamed endpoint in the sandbox create request and prints
its URL after the sandbox reaches Ready. --expose keeps the sandbox after
the create command returns and cannot be combined with --no-keep. Use
openshell service expose to add or update an endpoint later.
SDK create methods accept named or unnamed service exposures. An empty service name selects the unnamed endpoint. The returned sandbox includes a service URL map keyed by those names; use the empty key for the unnamed endpoint:
Expose a service that listens on loopback inside the sandbox:
Pass an optional service name to create a named service URL:
OpenShell strips the incoming Authorization header by default. Opt a service
into forwarding one valid bearer credential unchanged when the application
performs its own authentication:
For a create-time exposure, use both flags:
In bearer-passthrough mode, OpenShell accepts zero or one application
Authorization header. When present, it must contain a nonempty Bearer
credential. Missing credentials reach the application so it can return its own
authentication response. Duplicate, Basic, and malformed credentials fail with
400 Bad Request. Gateway and edge identity headers, proxy authorization, and
edge authentication cookies remain stripped.
Bearer passthrough delivers the caller’s credential to the sandbox service. Enable it only when that service is trusted to receive the credential. Exposed services still use the gateway listener and its TLS configuration in this release.
For example, Codex App Server can keep the raw capability outside the sandbox and receive only its SHA-256 verifier:
Clients send Authorization: Bearer $APP_SERVER_TOKEN in the WebSocket
handshake. Codex’s WebSocket transport is experimental and unsupported for
production workloads. It authenticates the handshake before the app-server
initialize request.
List exposed endpoints:
List endpoints for one sandbox:
Use structured output for automation:
Structured list output contains services and next_page_token fields. Each
record contains workspace, sandbox, service, target_port,
authorization_mode, and url.
The unnamed service uses an empty service string. Pass the returned token to
--page-token to continue. An empty result has an empty services collection.
Show or delete one endpoint:
Omit the service name to manage the unnamed endpoint:
Loopback gateways return local openshell.localhost URLs. Remote gateways
return HTTPS URLs on the gateway listener. Service routes bypass control-plane
RPC authorization and do not use OIDC or CLI login. Because they share the
listener in this release, its TLS configuration—including any required client
certificate—still applies. The application is responsible for authentication
enabled on its endpoint, and an upstream edge proxy may still apply its own
access policy. For gateway service-domain configuration, refer to
Manage Gateways.
Monitor and Debug
List all sandboxes:
Filter the list by labels when you want a narrower view:
Use -o json or -o yaml for machine-readable output:
Structured list output contains sandboxes and next_page_token fields.
Pass the returned token to --page-token to continue.
Get detailed information about a specific sandbox. The output lists Policy source (sandbox or global), Revision (the active policy’s row version for that source), and the formatted active policy YAML:
For automation, use --output json or --output yaml to get machine-readable sandbox details:
Print only the policy YAML for scripting (same effective policy, no metadata):
Diagnose Failed Calls to Tool Servers
A sandbox can be Ready while an agent’s call to an external tool server fails with fetch failed. Run openshell sandbox get my-sandbox and look under Tool server connections for the server’s address, Last result, and Reported at. This information is available for configured endpoints that use MCP over HTTP. It helps you find where the call failed without changing sandbox readiness.
JSON and YAML output include an endpoint_statuses list with last_reported_at. The protobuf API exposes the corresponding timestamp as Sandbox.status.endpoint_statuses[].last_reported_time; SDKs render it using their native or curated time representation. Select the endpoint by its address to read the result directly. Treat endpoint_id as opaque.
For example, use jq to select the tool server at tools.example.com, path /mcp, and port 443:
Read last_result to choose the next check:
NoObservedExchange: no result has been reported for the current configuration and supervisor session. Try the operation and inspect its logs if no result appears.HttpResponseReceived: the server returned a final HTTP status below 400, including a protocol upgrade. Informational responses alone do not establish success. Check the client’s response for protocol or tool errors; an HTTP 200 response can still contain an error.PolicyDenied: OpenShell denied the request, including MCP protocol-version, request-header, request-body, and HTTP-method checks. Check the sandbox policy and denial logs.CredentialUnavailable: required credentials were unavailable. Check the endpoint’s attached provider.TlsFailed: TLS setup or the handshake failed. Check certificates and TLS configuration.TransportFailed: the network exchange failed. Check name resolution, connectivity, and the server process.UpstreamRejected: the server returned an HTTP status of 400 or higher. Check the server’s response and logs.Unspecified: the result is unrecognized or absent. Do not infer success from it.
For example, a stopped server can produce TransportFailed while the sandbox stays Ready. After the server recovers, an HTTP response below 400 updates the result to HttpResponseReceived.
Results come from actual traffic and do not expire. A result can remain unchanged after the server stops until another observed exchange or configuration/session reset. Reports combine recent observations and can drop them when full, so this list is not a request history. The address remains available when a result resets to NoObservedExchange. To verify current tool availability, run the actual operation.
last_reported_at is the RFC 3339 rendering of the protobuf last_reported_time when the gateway accepted the result. It is empty until a result is reported. New accepted observations advance it, including repeated results; identical report retries do not. If a configuration reset supersedes a report whose acknowledgement was lost, the gateway can accept still-valid pending evidence again and advance this timestamp without another exchange.
Addresses use lowercase hosts, canonical paths, and sorted distinct effective ports. Equivalent addresses share one record that combines observations from all callers and ports; it does not establish that every caller or port works. A failure before the HTTP path is known, such as a TLS failure during CONNECT, updates status only when the host and port identify one distinct endpoint. If several configured paths share that host and port, inspect the logs for the failure.
Inspect Logs and Activity
Stream sandbox logs to monitor agent activity and diagnose policy decisions:
OpenShell Terminal combines sandbox status and live logs in a single real-time dashboard:
Use the terminal to spot blocked connections marked action=deny and provider-related proxy activity. If a connection is blocked unexpectedly, add the host to your network policy or update the attached provider profile. Refer to Manage Sandbox Policies for the workflow.
The dashboard has three panels stacked vertically: Gateways, Providers (or Global Settings), and Sandboxes. Navigate within a panel with Up/Down or j/k. At a list boundary the cursor overflows into the adjacent panel, skipping empty panels. Use Tab/Shift+Tab to cycle panels directly. Press h/l or Left/Right in the middle panel to switch between the Providers and Global Settings tabs.
The sandbox table’s NOTES column shows Invalid config when policy or provider configuration blocks provisioning. Open the sandbox detail view for the full rejection reason, or run openshell sandbox get <name> -o json. The note clears after the configuration is repaired; active port forwards remain listed.
Press c in the Sandboxes panel to create a sandbox. In the optional Command field, use quotes or backslashes to group arguments containing spaces. For example, /bin/sh -c "echo GOOD; read x" prints GOOD and waits for Enter. Invalid quoting keeps the form open without creating a sandbox. The field does not expand variables or evaluate shell operators; invoke a shell explicitly with sh -c when you need that behavior.
Port Forwarding
Forward a local port to a running sandbox to access services inside it, such as a web server or database:
OpenShell prints the local URL only after the forward listener is reachable. Background forwards must be tracked locally so openshell forward list and openshell forward stop can manage them.
CLI forwards run independently of SSH multiplexing settings in your SSH config.
Foreground forwards end when the command exits; only background forwards appear in forward list.
List and stop active forwards:
Use JSON or YAML output when inspecting tracked forwards from automation:
Structured output includes workspace, sandbox, bind_address, port,
pid, and alive. The alive boolean reports whether the tracked PID still
matches the expected workspace-scoped OpenShell SSH forward and immutable
sandbox identity; it does not probe the forwarded socket. When no forwards are
tracked, structured output returns an empty collection.
The default table colorizes the STATUS column only when both standard output and standard error are capable ANSI terminals; piping or redirecting either stream, or running under TERM=dumb, gives a plain-text table. Other styled output—including -v log lines, progress spinners, prompts, and error messages—is decided per stream, so redirecting one stream leaves the other styled. Prefer --output json for automation rather than matching on the table. Set NO_COLOR to any non-empty value or pass --color never to suppress ANSI formatting, and --color always to force it when piping into a pager. --color applies to every openshell command.
You can also forward a port at creation time with --forward:
SSH Config
Each sandbox has its own SSH host key. The key stays the same when the supervisor or container restarts, a pod is rescheduled, or the sandbox is stopped and started. Deleting the sandbox removes its key. Creating another sandbox with the same name creates a different key.
The gateway returns the public fingerprint in Sandbox.host_key_fingerprint
from sandbox creation, lookup, and listing, and in
CreateSshSessionResponse.host_key_fingerprint. The value uses OpenSSH’s
SHA256:<base64> format. Read it over an authenticated gateway connection,
pin it to the sandbox ID, and reject SSH connections that present a different
fingerprint. This also applies to clients that connect directly to the
supervisor’s SSH socket.
The gateway keeps the private key in its credential store and delivers it only to the supervisor. The sandbox workload cannot read the key. Drivers, gateway, and supervisor must use the matching OpenShell release. Older runtimes can return an empty fingerprint; an empty value cannot verify an SSH server’s identity.
The CLI and TUI continue to use their existing SSH connection settings. Exposing the fingerprint does not enable automatic host-key verification in those clients.
Generate an SSH config entry for a sandbox so tools like VS Code Remote-SSH can connect directly:
Append the output to ~/.ssh/config or use --editor on sandbox create/sandbox connect for automatic setup.
Transfer Files
Upload files from your host into the sandbox:
When you omit the destination, OpenShell discovers the sandbox’s working
directory and uploads there. For a named local directory, OpenShell preserves
the basename, matching scp -r and cp -r. If that directory already exists,
the upload merges into it and overwrites matching entries without deleting
unrelated entries.
OpenShell preserves symlinks during upload. A symlink arrives in the sandbox as a symlink with the same target path instead of an expanded copy of the target file or directory. Dangling symlinks are also preserved.
For a single file uploaded without Git filtering, an existing destination directory receives <directory>/<source-basename>, with or without a trailing /. A symlink to a destination directory is preserved, and the file is written inside its target. A missing nested destination without / at the end instead names the uploaded file. For example, openshell sandbox upload my-sandbox ./config.json /sandbox/settings.json --no-git-ignore writes settings.json; uploading to an existing /sandbox/settings directory writes settings/config.json. Successful unfiltered uploads print the resulting path. Git-filtered uploads still treat the destination as a directory.
Download files from the sandbox to your host:
When the sandbox-side source is a single file, the destination follows cp-style placement: if the destination already exists as a directory or ends with /, the file lands inside it as <dest>/<basename>; otherwise the file is written at the exact destination path.
The CLI discovers the sandbox’s canonical working directory and only allows
sandbox-side sources that resolve inside it. Paths that escape lexically, such
as /etc/passwd or /sandbox/../etc/passwd, and paths that escape through a
symlink are refused before any data is transferred. Relative sources are
resolved from the working directory; absolute sources within the same canonical
directory are also accepted.
You can also upload files at creation time with the --upload flag on
openshell sandbox create. Pass --upload multiple times to upload
several paths in a single command:
By default, uploads inside a Git repository respect .gitignore rules so that
build artifacts, dependency caches, and other ignored files are not transferred.
The CLI stops the affected upload if Git filtering fails or selects no files.
When the CLI confirms that the source is outside a Git work tree, it uploads
without filtering and warns that .gitignore rules are not applied. Git must
be installed to determine whether filtering applies. A broken or inaccessible
repository stops the upload. Pass --no-git-ignore to intentionally upload
without filtering. These rules apply to both sandbox upload and
sandbox create --upload.
sandbox create --upload checks every upload locally before it provisions the
sandbox. If any upload is rejected, including a missing path or a Git filtering
failure, the command exits with an error and creates no sandbox. If a transfer
fails after provisioning, the sandbox remains running and earlier uploads may
have completed. Use openshell sandbox upload to retry against the existing
sandbox, or openshell sandbox delete to remove it. Add --no-git-ignore to the
retry only if you intend to upload without filtering.
Uploads preserve symlinks, including dangling links, instead of dereferencing their targets. A symlink source bypasses Git filtering so the link itself is archived.
Stop and Start Sandboxes
Stop compute when you want to retain a sandbox and its persistent workspace without keeping its container, pod, or VM running:
The name is optional and defaults to the last-used sandbox. Stop stops local
background forwards and waits for the Stopped phase. Start waits until the
same sandbox returns to Ready. While stopped, you cannot connect, execute
commands, transfer files, forward ports, or reach exposed services. Policies,
provider attachments, settings, service definitions, and persistent workspace
data remain associated with the sandbox.
Stop and start are idempotent. You can also start a retained Completed or
Error/MainProcessFailed sandbox to launch a fresh instance of its canonical
command. Starting a fresh instance invalidates SSH sessions issued for the
previous runtime generation. Delete an inactive sandbox normally when you no
longer need its retained state.
Delete Sandboxes
Deleting a sandbox stops all processes, releases resources, and purges injected credentials.
The command can return while cleanup is pending. deletion accepted means the
gateway started deletion; inspect the sandbox until it disappears if your next
step requires completion. An already-absent sandbox is a successful no-op, but
missing workspaces and authorization failures remain errors. SDK callers can
inspect typed deletion outcomes.
Delete multiple sandboxes by listing their names in one command:
When a multi-sandbox delete fails for one entry, the CLI reports that sandbox’s failure and continues with the remaining names. The command exits with an error after it attempts every requested deletion if any entry failed.
Sandbox Lifecycle
Every sandbox moves through a defined set of phases:
Before workload activation, OpenShell validates the effective policy and matching
provider configuration. A rejection keeps the workload unstarted and exposes a
ConfigurationInvalid condition in Provisioning. Use openshell sandbox get
to inspect the diagnostic, then repair the policy or provider configuration.
Management operations remain available while startup is blocked. After repair,
the supervisor completes startup without recreating the sandbox. Starting a
stopped sandbox repeats configuration admission before launching its workload.
Image preparation and initial supervisor startup have an absolute deadline of 1800 seconds by default, independently of the CLI wait timeout. Operators can set image_preparation_timeout_seconds in [openshell.gateway] to any value from 1 through 86400. Each new create or explicit start stores its deadline. Progress events, configuration edits, and gateway or driver restarts cannot extend it. Changing the gateway setting affects new attempts only.
The first authenticated configuration report from the current supervisor ends preparation and starts a separate 300-second admission repair window. A slow image download therefore does not consume time reserved for fixing policy. During admission, an effective policy, settings, provider, profile, or attachment change resets the window from its stored change time. The first failed configuration load for that change grants another full window. Repeated failures, duplicate reports, and reconnects do not extend it; reaching Ready clears it.
Preparation expiry sets the sandbox to Error with reason ImagePreparationTimedOut; admission repair expiry uses ProvisioningTimedOut. The gateway stops its workload and supervisor compute, retaining the sandbox record, diagnostic, and restartable storage. Cleanup can remain pending if the backend is unavailable; the gateway retries it. Inspect provisioning in JSON output for the active phase, deadline, original preparation_deadline, admission_start_time, and cleanup timestamps. TUI NOTES identifies preparation timeout and distinguishes pending cleanup from reclaimed compute.
The gateway keeps each submitted create or start operation running after its caller disconnects, its deadline expires, or monitoring fails. This includes recovery and the stop/start sequence for automatic restart. While driver_operation_pending is true, explicit start, stop, and deletion are blocked; automatic restart keeps its schedule and waits. Timeout cleanup can try to stop partial compute, but it cannot declare completion until the original driver call returns and a subsequent stop succeeds. This prevents a stop that observed missing compute from allowing a retry just before the original create finishes. Uploaded rootfs archives remain available to the owned operation.
An actual driver response clears the pending flag, including ordinary rejection errors. The gateway retains driver_operation_id so a delayed response handler cannot change newer recovery work on the same provisioning attempt. Both fields appear in the provisioning JSON object. Backend behavior after a transport error still follows the driver’s existing contract. If the owning gateway crashes before recording the response, the flag and any protected upload can remain indefinitely; restart and age alone cannot establish that the operation finished. There is currently no API to resolve that lost ownership, so automatic cleanup completion, retry, and deletion remain blocked for that record.
For ImagePreparationTimedOut, inspect image preparation and supervisor startup diagnostics and check whether the configured preparation budget is sufficient. For ProvisioningTimedOut, repair the rejected configuration. In either case, wait for cleanup to complete, then explicitly restart:
Editing configuration after expiry does not restart compute. An explicit retry gets a new preparation deadline and a separate admission repair window, while static-policy restrictions from any previous activation remain in force. Attempts already active when the gateway is upgraded retain their stored deadline. Timed-out records are retained even for ephemeral creates; use sandbox delete when you no longer need the diagnostic or stored state.
The compute backend can become ready before the sandbox supervisor connects to
the gateway. During this interval, the sandbox remains in Provisioning and
reports a Ready=False condition with the reason SupervisorNotConnected.
After a gateway restart, an existing sandbox can return to Provisioning
temporarily while its supervisor reconnects. Wait for the phase to return to
Ready before you connect to the sandbox or execute commands.
The gateway records a successful canonical main-process exit as Ready=False
with reason MainProcessCompleted. Nonzero and signal-normalized results use
MainProcessFailed and the Error phase. It also sets status.exit_code;
signal exits use the standard 128 + signal convention. When restart policy
selects replacement, status.restart_count and status.next_restart_time
show the attempt number and next deadline. Inspect them with
openshell sandbox get <name>. sandbox stop cancels a pending replacement;
an explicit later start resets the restart count. Compute runtimes do not
apply their own restart policy.
Sandbox Runtimes
The gateway’s configured compute driver determines how OpenShell creates each sandbox. The CLI workflow stays the same across drivers: you create, connect to, inspect, and delete sandboxes through the gateway API.
For Docker, Podman, MicroVM, and Kubernetes behavior, refer to Sandbox Runtimes.
Next Steps
- To follow a complete end-to-end example, refer to the GitHub Sandbox tutorial.
- To select a workspace or understand access roles, refer to Workspaces.
- To supply API keys or tokens, refer to Manage Providers.
- To control what the agent can access, refer to Policies.
- To use the default runtime image, refer to Default Workload Image.