fix(sandbox): reset SandboxManager singleton before re-initializing in session_start#3
fix(sandbox): reset SandboxManager singleton before re-initializing in session_start#3onigunn wants to merge 1 commit into
Conversation
When /new fires, session_shutdown calls SandboxManager.reset() on the module-level singleton. The new session's session_start then calls initialize() on the partially-reset singleton, which crashes. Add a guarded SandboxManager.reset() call before initialize() to ensure the singleton is in a clean state regardless of whether session_shutdown ran or not.
There was a problem hiding this comment.
Pull request overview
This PR addresses a sandbox lifecycle issue when /new triggers session_shutdown followed by session_start in the same process, leaving the SandboxManager singleton in a reset/partial state and causing sandbox initialization to crash in the new session.
Changes:
- Adds a defensive
SandboxManager.reset()at the start of thesession_startinitialization flow to clear any stale singleton state before callinginitialize().
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| try { | ||
| // Reset any stale state from a previous session (e.g. after /new) | ||
| try { await SandboxManager.reset(); } catch {} |
There was a problem hiding this comment.
Empty catch {} blocks are likely to be flagged by the repo’s Biome linter (recommended rules) and also make it unclear that the error is intentionally ignored. Add an explanatory comment inside the catch block (or otherwise handle/log the error intentionally) to keep linting and intent consistent with the existing session_shutdown handler.
| try { await SandboxManager.reset(); } catch {} | |
| try { | |
| await SandboxManager.reset(); | |
| } catch { | |
| // Ignore reset failures: this is a best-effort cleanup before re-initializing. | |
| } |
| // Reset any stale state from a previous session (e.g. after /new) | ||
| try { await SandboxManager.reset(); } catch {} | ||
|
|
||
| await SandboxManager.initialize( | ||
| { | ||
| network: activeConfig.network, |
There was a problem hiding this comment.
sandboxFailed is set to true when initialization throws, but it is never cleared on a subsequent successful initialize() call (nor reset at the start of session_start). Since the bash tool checks if (sandboxFailed) throw, a single transient init failure can permanently block bash for the lifetime of the process, even after later successful initialization. Consider resetting sandboxFailed (and related flags like sandboxEnabled/sandboxInitialized) at the beginning of this try block and explicitly setting sandboxFailed = false on the success path.
| try { | ||
| // Reset any stale state from a previous session (e.g. after /new) | ||
| try { await SandboxManager.reset(); } catch {} | ||
|
|
||
| await SandboxManager.initialize( |
There was a problem hiding this comment.
This change introduces new lifecycle behavior (defensive SandboxManager.reset() on session_start) that isn’t currently asserted in tests. Since this file already has Vitest coverage, add a unit test that verifies reset() is attempted before initialize(), and that a reset() rejection is swallowed and does not prevent initialize() from being called (regression coverage for the /new ordering issue).
Problem
When the /new command fires, pi sends a session_shutdown event followed by a new session_start event. The sandbox extension's session_shutdown handler calls
SandboxManager.reset() on the module-level singleton. When the new session's session_start fires, it calls SandboxManager.initialize() on that same singleton — which is now in a
reset or partially-reset state. This causes a crash, blocking all bash commands in the new session (with the sandbox reporting a failed initialization).
The root cause is a lifecycle ordering issue: reset() tears down internal state that initialize() expects to either be fully constructed or completely absent. Since the singleton
persists across sessions within the same process, the stale/partial state from the previous session's shutdown poisons the next session's startup.
Related version
0.67.1
Fix
Add a guarded SandboxManager.reset() call at the top of the session_start handler's try block, before initialize():
The inner try/catch silently swallows errors — the reset is purely defensive. If the singleton is already clean (first session, or session_shutdown already ran), reset() is a
no-op or throws harmlessly. If the singleton has stale state, reset() clears it so initialize() starts fresh.
Testing