Skip to content

bug(server): after a compute-driver change the gateway deletes stopped sandboxes created by the previous driver #4769

Open

Description

@vineethsai7

User Story

I use OpenShell (0.1.3 and stock 0.1.5) on macOS with both the MicroVM and the Docker drivers available, one at a time, under a host-side policy agent (DefenseClaw, Apache-2.0).

I switched a gateway from compute_driver = "vm" to "docker" while a stopped, kept VM sandbox still held unpulled work. On v0.1.5 the gateway removed that sandbox from its store about 66 seconds after the switch. When I switched back to "vm", the VM driver still had the sandbox's disk, but OpenShell no longer listed it and could not start it, so the work was lost.

This affects anyone who changes the compute driver of a gateway that has kept sandboxes.

Problem Statement

The store reconcile loop compares every store record with the active driver's ListSandboxes. A record that the active driver does not list is treated as a vanished resource, even when that record was created by a different driver. The record carries the internal.openshell.ai/compute-driver annotation, which names its driver.

On the first sweep after the change, a Stopped, Stopping or Starting record is moved to Error with reason ComputeResourceMissing, and the gateway logs "Retained sandbox resource disappeared from the compute driver". On the next sweep the Error record is removed from the store, and a cleanup DeleteSandbox is sent to the new driver.

The grace period is measured from the record's creation time, not from when it went missing. Any sandbox older than five minutes is therefore removed within about two reconcile intervals.

Impact / Why This Matters

  • A stopped, kept sandbox, and any work in it that was not pulled out, is lost after a driver change, even though the old driver's resources still exist.
  • There is no warning at the switch and no way to keep the record.
  • The workaround is to pull every sandbox's work out and delete the sandboxes before changing the driver. That is easy to miss, and it is not documented.

Acceptance Criteria

  • After a change from driver A to driver B, records created by A are not moved to a terminal phase or deleted, and no cleanup for them is sent to B.
  • Such records are listed with a clear non-ready reason that names the driver that created them, for example "created on vm; the gateway runs docker".
  • Start, stop and exec on such a record fail with a clear precondition error. sandbox delete still removes the record.
  • After switching back to driver A, the sandbox can be started and its files are intact.
  • Records without a driver annotation keep today's behavior.

Reproduction Steps

  1. Start a gateway with compute_driver = "vm".
  2. Run openshell sandbox create --name keep --keep -- sh -c 'echo work > /sandbox/work.txt; sleep 600' from another terminal, then openshell sandbox stop keep while it is Ready. Wait until the sandbox is more than five minutes old. On v0.1.5 the original echo-only command entered Completed, so the sleep keeps it running until the stop.
  3. Stop the gateway, set compute_driver = "docker", and start it again.
  4. Watch the gateway log and openshell sandbox list:
    • within about one minute the log shows Retained sandbox resource disappeared from the compute driver, and keep is in Error;
    • about one minute later the log shows Removing sandbox from store, and keep is gone.
  5. Set compute_driver = "vm" and restart the gateway. openshell sandbox list does not show keep, and openshell sandbox start keep reports that it is not found. The VM driver's state directory still holds the sandbox's disk.

The same should reproduce on Linux by switching between docker and podman. I have not run that variant.

Expected: keep survives the switch in a non-destructive state and starts again after switching back.
Actual on v0.1.5: keep moved to Error on the first sweep and was deleted from the store 66 s after the switch (at record age 407 s).

Suggested UX (if applicable)

$ openshell sandbox list
NAME   PHASE        REASON
keep   Unavailable  created on compute driver "vm"; this gateway runs "docker"
$ openshell sandbox start keep
error: sandbox "keep" was created on compute driver "vm"; switch the gateway back to "vm" to start it

Environment

  • Stock OpenShell 0.1.5 release binaries, checksum verified.
  • macOS 15.8 (Darwin kernel 24.6.0), Apple silicon, Apple Hypervisor.framework/libkrun MicroVM driver; Docker Desktop 29.8.2; Podman not used.
  • One isolated local gateway on port 27670 with a SQLite store. The driver was changed in gateway.toml between restarts. The Docker phase ran under the local Docker Desktop user against the same isolated store; the VM disk remained in the isolated VM state directory.

Logs

2026-10-20T22:34:40.001901Z  WARN reconcile{otel.name="reconcile.sandboxes" driver.name=docker backend_count=0 store_count=3}: openshell_server::compute: Retained sandbox resource disappeared from the compute driver sandbox_id=<sandbox-id> sandbox_name=keep2 phase=Stopped
2026-10-20T22:35:40.017079Z  INFO reconcile{otel.name="reconcile.sandboxes" driver.name=docker backend_count=0 store_count=2}: openshell_server::compute: Removing sandbox from store after it disappeared from the compute driver snapshot sandbox_id=<sandbox-id> sandbox_name=keep2 age_secs=407
Error:   × code: 'Some requested entity was not found', message: "sandbox not found"

The last line is openshell sandbox start keep2 after switching back to VM. The old VM overlay disk was still present. The test used keep2 because the first, echo-only keep sandbox entered Completed.

Activity

  1. letv1nnn commented on Oct 22, 2026

    @letv1nnn
    Contributor

    📋 triage-agent

    Assessment: confirmed in source on main (39d95b42); one premise in the issue does not hold for the reported VM → Docker case.

    The User Story is first-hand (stock 0.1.5, real driver switch, logs). The deletion path matches current code in crates/openshell-server/src/compute/mod.rs:

    • reconcile_store_with_backend (:4810) sends every store record the active driver's ListSandboxes doesn't return to prune_missing_sandbox. It never looks at which driver created the record.
    • prune_missing_sandbox (:5960) measures the grace period from created_at_ms, not from when the record went missing, so any record older than ORPHAN_GRACE_PERIOD (5 min) is acted on in the first sweep. A Stopped/Stopping/Starting record moves to Error with ComputeResourceMissing. On the next sweep the Error record falls through to spawn_driver_sandbox_cleanup and deletion. That matches the logged 66 s.

    Gap: the issue says each record carries internal.openshell.ai/compute-driver. On main that annotation is only written by persist_runtime_binding (:2321), which runs only when the driver reports supports_sandbox_authentication. Of the first-party drivers, only Kubernetes does. Docker, Podman, VM and MXC report false. So VM-created records have no annotation, and with the fifth acceptance criterion ("records without a driver annotation keep today's behavior") the reported scenario would still delete the sandbox.

    A fix would therefore need to record the creating driver for every sandbox, not only authenticated ones, and decide what happens to existing records that have none. The annotation is also compared during sandbox authentication (grpc/auth_rpc.rs:136), so writing it more broadly should be checked against that path, or the driver name stored separately.

    Not reproduced end-to-end here (no VM + Docker switch run); the evidence above is source inspection plus the reporter's logs.

    Next human decision: whether a gateway should preserve records across a compute-driver change, and if so whether the creating driver is recorded for all sandboxes going forward and how legacy unannotated records are treated.

  2. letv1nnn commented on Oct 22, 2026

    @letv1nnn
    Contributor

    hey @vineethsai7, I'd like to work on this one, unless you've already started

  3. vineethsai7 commented on Oct 29, 2026

    @vineethsai7
    Author

    @letv1nnn , I haven't started on this yet. Feel free to take it :)

  4. vineethsai7 commented on Nov 2, 2026

    @vineethsai7
    Author

    Thanks for offering. I already have a fix for this one. It was #5071, which the bot closed before I was vouched. I'm opening a fresh PR today with the review feedback addressed and will link it here.

  5. vineethsai7 commented on Nov 2, 2026

    @vineethsai7
    Author

    Fresh PR with the review feedback addressed: #5133.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions