Why do you need it?
Snapshotting a Paused sandbox is always refused, on every runtime:
POST /v1/sandboxes/{id}/snapshots (sandbox state: Paused)
→ 409 {"code":"SNAPSHOT::INVALID_SOURCE_STATE","message":"Snapshot can only be created from a Running sandbox."}
The check is PersistedSnapshotService._ensure_source_sandbox_running
(snapshot_service.py). It
applies to every runtime and is asserted by a test. On Kubernetes that makes sense: pause is itself
snapshot-based, so a Paused sandbox has no live container left to commit.
On the Docker runtime, though, pause is an in-place freeze, and the Docker snapshot path is just
container.commit(...)
(docker/snapshot_runtime.py).
docker commit works fine on a paused container: by default Docker pauses a container itself for the
duration of a commit. So on Docker the refusal is a policy choice, not a technical limit.
Repro (Docker runtime, gVisor runsc as the secure runtime):
- Create a sandbox and write
/root/paused-proof.txt in it.
POST /v1/sandboxes/{id}/pause. OpenSandbox reports Paused, and Docker reports paused=true.
POST /v1/sandboxes/{id}/snapshots returns 409 SNAPSHOT::INVALID_SOURCE_STATE.
docker commit of the same paused container succeeds in 8.6s. The container stays paused=true,
and docker run <image> cat /root/paused-proof.txt prints written-before-pause.
- After
resume, the same snapshot request returns 202.
Environment:
opensandbox/server:latest (image from 2026-07-12; the check is unchanged on main at bdfcd01)
execd v1.0.19
- Docker 29.4.2,
overlayfs
- runsc
release-20260817.0 with --overlay2=none
Why it matters: we build a swarm control plane on OpenSandbox
(alineo), where agents fork children from their own live sandbox via
this snapshot API.
- Forks under a paused parent: an operator pausing a subtree is common, and every fork that lands
under a paused parent fails. We currently hold the fork until the parent resumes, which
unfreezes it just to copy its filesystem.
- Consistent checkpoints: the natural way to checkpoint a group of sandboxes consistently is
pause-all, snapshot-each, resume-all. That's impossible while snapshots require Running.
How could it be?
Make the source-state check runtime-aware, e.g. a runtime capability such as
supports_snapshot_from_paused():
- Docker: accept
Running and Paused. container.commit needs no change; committing an
already-paused container leaves it paused.
- Kubernetes: keep refusing
Paused as today.
Transitional states (Pausing, Resuming) can keep returning 409.
Other related information
Why do you need it?
Snapshotting a Paused sandbox is always refused, on every runtime:
The check is
PersistedSnapshotService._ensure_source_sandbox_running(snapshot_service.py). It
applies to every runtime and is asserted by a test. On Kubernetes that makes sense: pause is itself
snapshot-based, so a Paused sandbox has no live container left to commit.
On the Docker runtime, though, pause is an in-place freeze, and the Docker snapshot path is just
container.commit(...)(docker/snapshot_runtime.py).
docker commitworks fine on a paused container: by default Docker pauses a container itself for theduration of a commit. So on Docker the refusal is a policy choice, not a technical limit.
Repro (Docker runtime, gVisor
runscas the secure runtime):/root/paused-proof.txtin it.POST /v1/sandboxes/{id}/pause. OpenSandbox reportsPaused, and Docker reportspaused=true.POST /v1/sandboxes/{id}/snapshotsreturns 409SNAPSHOT::INVALID_SOURCE_STATE.docker commitof the same paused container succeeds in 8.6s. The container stayspaused=true,and
docker run <image> cat /root/paused-proof.txtprintswritten-before-pause.resume, the same snapshot request returns 202.Environment:
opensandbox/server:latest(image from 2026-07-12; the check is unchanged onmainatbdfcd01)execdv1.0.19overlayfsrelease-20260817.0with--overlay2=noneWhy it matters: we build a swarm control plane on OpenSandbox
(alineo), where agents fork children from their own live sandbox via
this snapshot API.
under a paused parent fails. We currently hold the fork until the parent resumes, which
unfreezes it just to copy its filesystem.
pause-all, snapshot-each, resume-all. That's impossible while snapshots require
Running.How could it be?
Make the source-state check runtime-aware, e.g. a runtime capability such as
supports_snapshot_from_paused():RunningandPaused.container.commitneeds no change; committing analready-paused container leaves it paused.
Pausedas today.Transitional states (
Pausing,Resuming) can keep returning 409.Other related information