Repository navigation
fix(sandbox): stop sandboxes from losing sessions when they stop - #248
Merged
Merged
Conversation
A dev build takes its channel from `git branch --show-current`, and the server files its database as `opencode-<channel>.db` for any channel that is not a release one. The sandbox guest runs the host's bundle, so the guest was re-keyed to whatever branch the host repo had checked out: every branch switch pointed it at a fresh, empty database and every session in the sandbox vanished from the UI. Nothing was lost — one guest here had accumulated opencode-main.db, opencode-work-folder-tabs.db and a third — but nothing could be reached either. Guests now set CODEV_DISABLE_CHANNEL_DB=1, which the server already honours. Packaged builds already resolved to the same opencode.db, so this only changes what a dev build does. The trade is one guest database shared across branches: a branch that adds a migration migrates it for the others. Sessions written before this change are still in their per-branch files under ~/.local/share/codev in the guest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sessions created in a sandbox disappeared when it stopped. Stopping one is a power cut — the vz helper calls VZVirtualMachine.stop(), which Apple documents as equivalent to pulling the power cable, and the guest gets no chance to sync — while the server's database runs WAL with synchronous=NORMAL, which is durable across a crashed process but not across a machine that goes away. Every commit still in the guest's page cache died with the VM, so a Stop/Start, a restart to apply a mount, or a force quit took the newest sessions with it. Measured on a real vz guest: a file written and left unsynced is gone after a plain Stop/Start, and so was a session created seconds earlier. `PRAGMA synchronous` now reads CODEV_SQLITE_SYNCHRONOUS, whitelisted because the value lands in a PRAGMA and defaulting to NORMAL so nothing changes for anything that does not set it. Sandbox guests set FULL. The knob has to live in core because the guest runs the host's own server bundle. After: a session created two seconds before `kill -9` of the whole app is still there on the next start. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Force quitting the app left the broker's unix socket file in place, and the next start failed with EADDRINUSE binding onto it — the sandbox could not start at all until someone found the file in the temp directory and deleted it. Hit while force quitting the app to test the durability fix, so it is exactly the state a crash leaves behind. The vz helper already unlinks its own socket before binding; the broker's is bound in TypeScript and did not. It now clears the file, but only when nothing answers on it: a socket with a live listener behind it belongs to another instance of the sandbox, and failing loudly is the right outcome there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
force-pushed
the
fix/sandbox-guest-db
branch
from
August 17, 2026 14:25
6cf4196 to
aa5791c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sessions created in a sandbox disappeared. Three separate causes, all found by
taking a shell inside a running vz guest (the server's own PTY API) and reading
its database and filesystem directly.
1. The guest database was keyed to the host's git branch
A dev build takes its channel from
git branch --show-current, and the serverfiles its database as
opencode-<channel>.dbfor any channel that is not arelease one. The guest runs the host's bundle, so it inherited that: every time
the host repo changed branch, the guest pointed at a fresh, empty database. The
guest I looked at had three —
opencode-main.db,opencode-work-folder-tabs.dband one more — each holding the sessions from whichever branch was checked out
when they were made.
Nothing was lost, but nothing could be reached either. Guests now set
CODEV_DISABLE_CHANNEL_DB=1. Packaged builds already resolved to the sameopencode.db, so this only changes dev builds. The trade is one guest databaseshared across branches: a branch that adds a migration migrates it for the rest.
2. Guest writes never reached the disk
This is the one that actually loses data, and it affects packaged builds too.
Stopping a sandbox is a power cut:
native/vzhelpercallsVZVirtualMachine.stop(), which Apple documents as equivalent to pulling thepower cable, and the guest gets no chance to sync. The server's database runs
WAL with
synchronous = NORMAL— durable across a crashed process, not across amachine that goes away — so every commit still sitting in the guest's page cache
died with the VM. Stop a sandbox, add a folder to one (which restarts it), or
force quit the app, and the sessions from the preceding seconds were gone.
Measured before the fix: a file written in the guest and left unsynced is gone
after a plain Stop/Start, and so is a session created seconds earlier.
PRAGMA synchronousnow readsCODEV_SQLITE_SYNCHRONOUS— whitelisted, sincethe value lands in a PRAGMA, and defaulting to
NORMALso nothing changes foranything that does not set it. Sandbox guests set
FULL. The knob has to livein core because the guest runs the host's own server bundle; there is no
sandbox-only build to patch.
Measured after: a session created two seconds before
kill -9of the whole appis still there on the next start.
3. A force quit left the sandbox unable to start
Found while testing #2. Force quitting the app leaves the broker's unix socket
file behind, and the next start fails binding onto it:
The sandbox stays unstartable until someone deletes that file by hand. The vz
helper already unlinks its own socket before binding; the broker's is bound in
TypeScript and did not. It now clears the file, but only when nothing answers on
it — a live socket belongs to another instance of the sandbox, and failing
loudly is right there.
Verification
All against the real vz backend, driving the running dev app:
kill -9of the app twoseconds after being created.
opencode.db, which survives a host branch switch(checked by switching branches and re-reading the database in the guest).
steal one that still has a listener.
packages/codev-sandbox147 tests pass,packages/opencode3197 pass with thepragma change,
packages/coretypechecks, oxlint clean.DIVERGENCES.mdgainsa row for the two core files,
CHANGELOG.mdtwo entries, and the sandbox READMErecords both guest env vars and the remaining gap.
Not fixed here
The power-cut stop itself. Durable commits cover the server's database, but
anything else the guest writes to its own disk in the last seconds still goes.
A graceful shutdown — the helper asking the guest to power down, the guest
syncing first — needs a listener baked into the guest image, so it is worth
doing the next time the image is rebuilt. Noted in the README's known gaps.
Sessions written before this lands are still in their per-branch database files
under
~/.local/share/codevinside the guest; say the word and I can pull themacross.
🤖 Generated with Claude Code