Skip to content

fix(sandbox): reset SandboxManager singleton before re-initializing in session_start#3

Open
onigunn wants to merge 1 commit into
sysid:mainfrom
onigunn:fix/sandbox-reset-before-init
Open

fix(sandbox): reset SandboxManager singleton before re-initializing in session_start#3
onigunn wants to merge 1 commit into
sysid:mainfrom
onigunn:fix/sandbox-reset-before-init

Conversation

@onigunn
Copy link
Copy Markdown

@onigunn onigunn commented Apr 14, 2026

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():

  try {
      // Reset any stale state from a previous session (e.g. after /new)
      try { await SandboxManager.reset(); } catch {}

      await SandboxManager.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

  • Verified the diff is a minimal 3-line addition with no behavioral changes to the happy path.
  • The session_shutdown handler already wraps reset() in try/catch, so this is consistent with the existing pattern.

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.
Copilot AI review requested due to automatic review settings April 14, 2026 11:34
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the session_start initialization flow to clear any stale singleton state before calling initialize().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/sandbox/index.ts

try {
// Reset any stale state from a previous session (e.g. after /new)
try { await SandboxManager.reset(); } catch {}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
try { await SandboxManager.reset(); } catch {}
try {
await SandboxManager.reset();
} catch {
// Ignore reset failures: this is a best-effort cleanup before re-initializing.
}

Copilot uses AI. Check for mistakes.
Comment thread packages/sandbox/index.ts
Comment on lines +399 to 404
// Reset any stale state from a previous session (e.g. after /new)
try { await SandboxManager.reset(); } catch {}

await SandboxManager.initialize(
{
network: activeConfig.network,
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread packages/sandbox/index.ts
Comment on lines 398 to 402
try {
// Reset any stale state from a previous session (e.g. after /new)
try { await SandboxManager.reset(); } catch {}

await SandboxManager.initialize(
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
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.

2 participants