Repository navigation
feat: consistent sandbox access after the main process exits #4949
Description
User Story
As a user running a command in an OpenShell sandbox, I want to inspect a retained sandbox after its main process exits, with exec, file transfer, port forwarding and exposed services, so that I can collect results and debug failures without restarting the workload.
Problem Statement
After the main process exits, a retained sandbox moves to Completed (exit 0) or Error with MainProcessFailed (nonzero exit). The supervisor deliberately keeps its access plane running. Whether you can still reach the sandbox currently depends on which command you use and which gateway replica receives it:
| Operation | Ready | Completed / Error, supervisor still connected |
|---|---|---|
SSH config, upload / download |
allowed | allowed |
sandbox connect |
allowed | fails: it attaches to the main process, which has exited |
forward start, forward service |
allowed | allowed (the CLI's service-forward check stops at the next 2 s recheck) |
sandbox exec |
allowed | refused by the CLI and the gateway: sandbox is not ready |
| Exposed services (HTTP routing) | allowed | refused: sandbox not ready |
There are also two related inconsistencies:
- On a multi-replica gateway, the post-exit check only looks at the receiving replica's in-memory session map. A request that lands on a replica that doesn't hold the supervisor session is refused. A
Readysandbox doesn't have this problem, because relay opening already routes to the owning replica. Replica routing at the edge (grpcRoute.replicaRouting.enabled) is off by default, so requests land on any replica. - The docs and the CLI skill say access stops once a sandbox completes, which isn't true for SSH or file transfer.
Impact / Why This Matters
- Users who run a batch command with
--detachcansshin anddownloadresults, butopenshell sandbox exec -- cat result.txtfails. For agents driving OpenShell programmatically, exec is the primary interface. - An exposed service started after the main process exits stops being reachable, even though
forward serviceto the same port works. - On multi-replica gateways, post-exit access works only some of the time, depending on load-balancer placement.
- Without a stated rule, each new access path decides for itself, and the inconsistencies come back.
Proposed Design
Lifecycle
stateDiagram-v2
direction LR
[*] --> Provisioning: create
Provisioning --> Ready: supervisor connected
Provisioning --> Error: preparation or admission timed out
Ready --> Provisioning: supervisor disconnected
Ready --> Completed: main process exits 0
Ready --> Error: main process exits nonzero
Ready --> Stopping: stop
Error --> Stopping: stop
Stopping --> Stopped
Stopped --> Starting: start
Completed --> Starting: start or restart policy
Error --> Starting: start or restart policy
Starting --> Ready: new supervisor connected
Completed --> Deleting: delete, or --no-keep cleanup
Error --> Deleting: delete, or --no-keep cleanup
Stopped --> Deleting: delete
Ready --> Deleting: delete
Deleting --> [*]
Error has two causes. MainProcessFailed records a nonzero exit code in status.exit_code. An infrastructure failure, such as ImagePreparationTimedOut, ProvisioningTimedOut or a backend-reported failure, has no main-process result. start accepts Stopped, Completed, an Error with MainProcessFailed, or an Error from a provisioning timeout once cleanup has finished. stop on a Completed sandbox only revokes sessions.
Rules
-
One access rule for every path into a sandbox. Exec (unary and interactive), SSH sessions (SSH config, file transfer, and
connect), TCP forwarding (forward start,forward service) and exposed-service HTTP routing all use the same check:Ready: allowed.CompletedorError: allowed while the sandbox's supervisor session is live.- Every other phase (
Provisioning,Starting,Stopping,Stopped,Deleting): refused withFAILED_PRECONDITIONsandbox is not ready, or HTTP 412sandbox not readyfor exposed services.
-
Access follows the supervisor, not the exit code. After the main process exits, the supervisor keeps its access plane, so reachability depends on whether the supervisor is still connected. Exit 0 and nonzero exits behave the same. An infrastructure
Errorusually has no supervisor left, because timeout cleanup stops compute, so it is refused. If the supervisor survived, access is allowed for debugging.sandbox connectpasses this check but then fails, because it attaches to the main process and that process has exited.sandbox exec --tty -- /bin/bash -lgives an interactive shell instead. -
The answer doesn't depend on the replica. "Live" means a session on the receiving replica, or a fresh supervisor owner record that names another replica. Relay opening already routes to that replica, so any replica gives the same answer.
-
The gateway decides. Clients may reject only phases where access can never succeed, and must leave the supervisor check to the gateway. The CLI accepts
Ready,CompletedandErrorand forwards the request. -
Retention decides whether the sandbox still exists.
- Retained (the default): after the main process exits, the record and compute stay until you
delete, orstarta new run. Rule 1 governs access in the meantime. - Ephemeral (
--no-keep): the gateway deletes the sandbox after the main process exits, whether the exit code is 0 or not, once terminal output has been delivered. Access ends with the delete.
- Retained (the default): after the main process exits, the record and compute stay until you
-
A new run revokes old access. Starting a
CompletedorMainProcessFailedsandbox, manually or by restart policy, invalidates SSH sessions from the previous runtime generation.
Acceptance Criteria
-
sandbox exec(unary and interactive) succeeds on a retainedCompletedorError/MainProcessFailedsandbox while its supervisor is connected, and is refused once the supervisor has gone. - Exposed services route to a retained
CompletedorErrorsandbox while its supervisor is connected. - On a multi-replica gateway, exec, SSH, forwarding and exposed services to a
Completedsandbox succeed whichever replica receives the request. - The CLI no longer rejects exec or service forwards on
CompletedorErrorbefore asking the gateway. - A conformance scenario certifies, for exit codes 0 and nonzero:
--no-keepsandboxes are deleted after exit, and retained sandboxes recordCompletedorErrorwith the exit code and still accept exec. - Published docs and the CLI skill describe access after exit for retained and ephemeral sandboxes.
Alternatives Considered
- Keep exec limited to
Ready. This leaves exec inconsistent with SSH and file transfer, which already work after exit, and agents would have to fall back to SSH to read results. - Allow access after exit only for
CompletedandError/MainProcessFailed, and refuse every infrastructureError. This is tighter, but it removes the debugging shell in the rare infrastructure failure where the supervisor survives. With rule 2, the supervisor check already refuses the common case, where compute is gone. - Rely on edge replica routing to reach the owner. This is off by default and documented as a latency optimization. Correctness shouldn't depend on it.
Agent Investigation
- The SSH and forward check (
sandbox_relay_reachable, added in fix(sandbox): complete successful main processes #2884) already allowedCompletedandErrorwith a live supervisor. The exec handlers andservice_routing.rswere not updated. - Live checks on Docker and Kubernetes (single replica): after exit, SSH and download worked on both
CompletedandError, and exec was refused by the CLI pre-check.
Out of scope, to be tracked separately:
- After a Docker gateway restart, the supervisor container of each
CompletedorErrorsandbox is removed and not recreated, so the sandbox becomes unreachable even though its data is still on disk. Kubernetes is not affected. starton such a Docker sandbox fails until it is stopped and started again.
Activity