Repository navigation
Description
Summary
A sandbox can end up with the gateway's Sandbox record at phase Provisioning
while the backend Sandbox CR is at spec.replicas: 0 with no pod. In that state
there is no supported recovery path:
sandbox startis rejected — the phase gate only admitsStopped,
Completed,Starting, a failed-main-processError, or a provisioning timeout.sandbox stopis rejected — it requiresReady.- The reconcile sweep does not correct the record: with
replicas: 0/ no pod, the
Provisioningcase falls through to the reconcile/derive_phasedefault and the
stale value is perpetuated rather than corrected.
The only exits are delete + recreate, or out-of-band edits to the gateway's store.
This is a distinct variant of the "unrecoverable phase" bugs #3308
and #4209, which are Error-phase. Here the record is
Provisioning, so the recovery work in #4231 (allow stop from
Error) does not reach it, and start still rejects it. The closest mechanism match is
#2932 (a stale condition perpetuated by reconcile), fixed by
#2933.
Environment
- OpenShell:
0.1.2(gatewayghcr.io/nvidia/openshell/gateway:0.1.2, supervisor0.1.2, CLI0.1.2) - Driver: Kubernetes (agent-sandbox
v0.4.6, CRDsandboxes.agents.x-k8s.iov1alpha1) - Kubernetes:
v1.27.3(kind)
Reproduction
Non-deterministic (a race). Observed triggers:
- A node/machine reboot while the gateway is bootstrapping a sandbox — the gateway pod
restarts during cluster stabilization, and a transient dependency failure fires
suspend_sandbox_runtime_after_dependency_failure, resettingreplicas: 0while the
record is stillProvisioning. - A
stopimmediately followed bystart, leaving the record mid-transition.
Observed state
$ openshell sandbox list
NAME ... Provisioning
$ kubectl get sandboxes -n <ns> -o yaml
# spec.replicas: 0
# no openshell.ai/sandbox-runtime-bootstrapping annotation
# (no sandbox pod in the namespace)
$ openshell sandbox start <name>
Error: sandbox must be Stopped, Completed, or a failed main-process Error to start (current phase: Provisioning)
$ openshell sandbox stop <name>
Error: sandbox must be Ready to stop (current phase: Provisioning)Root cause (pointers; as of e1f3c82ca)
- The record's
status.phaseisProvisioningbut the backend CR is empty.
start_sandboxrejects it because the gate only admits
Stopped | Completed | Starting | is_failed_main_process_result | provisioning_timeout
(crates/openshell-server/src/compute/mod.rs,start_sandbox, ~L1784). - The reconcile sweep re-derives phase from the driver snapshot; with no pod /
replicas: 0, theProvisioningcase falls through to the_ => phasedefault
(~L6564, fed byderive_phase~L6863), so the stale value is kept. suspend_sandbox_runtime_after_dependency_failure
(crates/openshell-driver-kubernetes/src/driver.rs, ~L4111) is what drives
replicas: 0on the dependency-failure path.
Expected
One of:
- The reconcile loop corrects a
Provisioningrecord whose backend reports the
sandbox as empty (no pod /replicas: 0) instead of perpetuating it — cf. fix(gateway): let Ready win over stale Suspended in derive_phase #2933, which
madederive_phaseletReadywin over a staleSuspended; or startaccepts a staleProvisioningwhose backend is empty and routes it through the
existing idempotent-restart path — reusing the persisted identity and re-attaching the
existing workspace PVC — the same way theStartingbranch already does (~L1800).
Actual
No recovery short of delete + recreate. An out-of-band edit of the gateway store
(flipping Sandbox.status.phase from Provisioning to Stopped) does make start
succeed and preserves the workspace — confirming this is a fixable record desync rather
than data loss — but it is not a supported operator path.
Proposed direction
- Extend fix(compute): allow stopping errored sandboxes for recovery #4231's recovery approach to the
Provisioning-with-empty-backend case (not
justError), or - correct the stale
Provisioninginderive_phase/reconcile (precedent: fix(gateway): let Ready win over stale Suspended in derive_phase #2933).
Either way, start from this state should reuse the persisted identity and the existing
workspace PVC (idempotent restart), not require a delete.
References
- Sandbox stuck in unrecoverable Error phase after supervisor session drops (SSH disconnect); stop/start refuse to act #3308 — unrecoverable
Error, stop/start refuse (same symptom class) - bug(vm-driver): a sandbox that was never stopped ends in unrecoverable
Errorafter a gateway restart #4209 — gateway restart → unrecoverableError(same trigger family) - fix(compute): allow stopping errored sandboxes for recovery #4231 — open: allow stopping errored sandboxes for recovery (in-flight recovery mechanism)
- Stop→start leaves sandbox stuck at Starting on Agent Sandbox v1beta1 (stale Suspended condition read as Stopped) #2932 / fix(gateway): let Ready win over stale Suspended in derive_phase #2933 — stale condition perpetuated by reconcile; fix in
derive_phase(closest mechanism) - fix(kubernetes): serialize lifecycle cleanup with sandbox restart #4078 — serialize lifecycle cleanup with sandbox restart (related stop/start race)
Activity
graemes commentedon Oct 15, 2026
Another data point: we hit this on two consecutive clean reboots of a single-node Kubernetes host (OpenShell 0.1.2, Kubernetes v1.36.5, kubeadm, aarch64), so for us it's deterministic on reboot rather than a one-off.
Same shape both times:
openshell sandbox list→Provisioning; the Sandbox CR is atspec.replicas: 0, with no sandbox or supervisor podopenshell sandbox get→Ready: False (DependenciesNotReady) - sandbox-runtime enforcement dependencies are not readystartrefuses (not Stopped) andstoprefuses (not Ready), as described above; restarting the gateway does not helpThe only recovery we found was to edit the gateway store directly:
status.phasefromPROVISIONING(1) toSTOPPED(7) in its protobuf payload — a single varint byte;openshell sandbox start.The sandbox is
Readyabout 10 s later, with workspace PVCs intact. We now run that from a systemd timer that triggers only on this exact shape (Provisioning +replicas: 0+ no pod, held for 10 min). That works, but it's obviously not something users should need.We checked the v0.1.3 and v0.1.4 release notes and didn't spot a fix for this specifically. Two things that would close it for us:
startaccepts (orstartshould acceptProvisioningwhen replicas is 0);graemes commentedon Oct 15, 2026
Follow-up: still reproduces on v0.1.4.
We installed a parallel 0.1.2 gateway in its own namespace,
helm upgraded it to 0.1.4, and rebooted the node. It had two sandboxes: one created on 0.1.2 that survived the upgrade, and one created fresh on 0.1.4 (supervisor and sandbox runtime 0.1.4). After the reboot, both came back atreplicas: 0/Provisioningwith the sameDependenciesNotReady - sandbox-runtime enforcement dependencies are not ready. Neither recovered on its own in 24+ minutes.The
status.phase1 → 7 store edit from my comment above still recovers a 0.1.4 sandbox (Stopped, thensandbox start, then Ready). The proto field numbers it relies on are unchanged between v0.1.2 and v0.1.4.github-actions commentedon Nov 13, 2026
This issue has had no activity for 28 days and is now marked stale. It may be closed in 14 days if there is no further activity. Comment or remove the state:stale label to keep it open.