Skip to content

fix(app): keep sandbox tabs through a restart, and stop the freeze it caused - #247

Merged
quickbeard merged 2 commits into
mainfrom
fix/sandbox-restart-tabs
Aug 17, 2026
Merged

quickbeard merged 2 commits into
mainfrom
fix/sandbox-restart-tabs

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Mounting a new folder on a sandbox that had one of its sessions open froze the
window: no clicks, no typing, force quit the only way out. Fixing that exposed
the behaviour underneath it — a sandbox restart threw away every tab it owned —
so this does both.

The freeze

Applying a mount has no hot path on macOS, so the guest restarts, and a sandbox
is published as a connection only while it is ready: it leaves server.list for
the second or two the restart takes. Two effects then disagree about the same
tab — context/tabs.tsx prunes tabs whose server is not in the list, and the
titlebar adds one back for whatever session the route names. Prune, re-add,
prune, about a thousand rounds a second. The loop is synchronous, so the
renderer never returns to the event loop and never processes the IPC event that
would put the sandbox back and end it, which is why it never recovered on its
own. Any sandbox restart could trigger it, not only mounting a folder.

The titlebar effect now skips servers that are not connected.

Tabs that survive the restart

Tabs belonged to a sandbox for as long as it answered. They now belong to it
for as long as it exists, so a restart no longer closes them or discards each
tab's memory — the prompt you had typed, your terminal, your scroll position.
The renderer passes every configured sandbox as retainedServers, the server
context exposes it as retained, and both prune effects reconcile against
list plus retained. Deleting a sandbox still clears its tabs:
dialog-select-server removes them explicitly, and the sandbox leaves
retained at the same time.

The route is the other half. A retained server has no connection, and mounting
the SDK and sync providers with undefined sends the session's requests to
whichever server happens to be selected — which answered "this session cannot be
found" about a healthy session, and bounced the route home through a burst of
~250 navigations when a tab was opened while its sandbox was stopped.
TargetServerRoute now renders a Waiting for this server panel instead, and
swaps back to the session by itself once the sandbox answers.

WSL servers are deliberately left as they are: same latent problem, no
equivalent restart in normal use.

Verification

Driven against a real vz sandbox in the dev app (CDP into the renderer, the
sandbox IPC channel for the mount change):

  • Before: the mount change wedged the renderer; instrumentation showed the tab
    list oscillating and the command catalog effect re-running ~1000×/s, matching
    the renderer unresponsive stack samples from the original report.
  • After: the mount applies in ~1.5 s, the window stays responsive, the session
    tab never leaves the strip, the view shows the waiting panel for ~1.2 s and
    returns to the session on its own.
  • A tab opened while the sandbox is stopped shows the waiting panel and no
    longer bounces home; starting the sandbox swaps it back with no interaction.
  • Tabs also survive an app relaunch with the sandbox stopped.

bun typecheck clean in packages/app and packages/desktop; app (675),
browser (69) and desktop (97) tests pass. DIVERGENCES.md gets a row per
change and CHANGELOG.md an entry for each.

🤖 Generated with Claude Code

quickbeard and others added 2 commits August 16, 2026 21:46
Mounting a new folder on a sandbox that had one of its sessions open froze
the whole renderer: no clicks, no typing, force quit the only way out.

Applying a mount has no hot path on macOS, so the sandbox restarts, and a
sandbox is published as a connection only while it is ready — it leaves
server.list for the second or two it takes. Two effects then disagree: the
tab store prunes tabs whose server is not in the list, and the titlebar adds
a tab back for whatever session the route names. Prune, re-add, prune, about
a thousand rounds a second. The loop is synchronous, so the renderer never
returns to the event loop and never processes the IPC event that would put
the sandbox back and end it.

The titlebar effect now skips servers that are not connected. The route is
left alone, so the tab comes back on its own once the sandbox does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A sandbox is a connection only while it is ready, so restarting one — to
apply a folder you just mounted, or because you stopped and started it —
takes it out of server.list for a second or two. The tab store reconciles
against that list, read every restart as "this server is gone", and deleted
every tab belonging to the sandbox along with each tab's memory: the typed
prompt, the terminal, where you were scrolled.

Tabs now belong to a sandbox for as long as the sandbox exists rather than
as long as it answers. The desktop renderer passes every configured sandbox
as retainedServers, the server context exposes it as `retained`, and both of
the tab store's prune effects reconcile against list plus retained. Deleting
a sandbox is unaffected: dialog-select-server already removes its tabs
explicitly, and the sandbox then leaves retained as well.

The route is the other half. A retained server has no connection, and
mounting the SDK and sync providers with undefined sends the session's
requests to whichever server happens to be selected — which answered "this
session cannot be found" about a perfectly healthy session, and bounced the
route home through a burst of ~250 navigations when a tab was opened while
its sandbox was stopped. TargetServerRoute now renders a "Waiting for this
server" panel instead, and swaps back to the session by itself the moment
the sandbox answers again.

Verified end to end against a real vz sandbox: mounting a folder with a
session open keeps the tab, shows the waiting panel for ~1.2s, and returns
to the session with no interaction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard merged commit d8c45aa into main Aug 17, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the fix/sandbox-restart-tabs branch August 17, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant