fix(pi): await MCP bridge bootstrap in before_agent_start#490
Merged
Conversation
Pi subagents (`pi --mode json -p --no-session`) spawn a fresh process that loads the context-mode extension and immediately fires `before_agent_start` to dispatch the LLM call. The MCP bridge bootstrap (spawn server.bundle.mjs → initialize → tools/list → pi.registerTool × N) was fire-and-forget via `_mcpBridgeReady`, so the LLM call went out with an empty ctx_* tool registry and the routing block (~2.5K tokens) became dead weight — the LLM was told to call `ctx_execute` / `ctx_search` / etc. but Pi had not yet registered them. Awaiting `_mcpBridgeReady` at the top of the `before_agent_start` handler closes the race: by the time the handler resolves, the bridge has settled (success or failure — failures are still logged to stderr but never propagated, matching the original best-effort contract) and the registry contains the ctx_* tools. Reported in mksglu#472 (comment 4412197109). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…up flake The default 10s vitest hookTimeout is exhausted on Windows runners by files whose afterAll loops over many better-sqlite3 handles — tests/session/session-pipeline.test.ts is the canonical example. Local runs finish in ~500ms but Windows fork-pool contention plus native addon cleanup can stretch past 10s, surfacing as FAIL tests/session/session-pipeline.test.ts Error: Hook timed out in 10000ms. Match the 30s testTimeout already in this config so the cleanup window matches the work window — same envelope better-sqlite3 needs for tests themselves. No change to local-dev wall time (only fires when a hook exceeds 10s, which is a flake-level event). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
What
Make the Pi adapter's
before_agent_starthandlerawait _mcpBridgeReadybefore returning so the LLM call dispatched right after the handler sees thectx_*tools in Pi's registry.Why — race reported in #472 (comment 4412197109)
The reporter's diagnosis is correct. In
src/adapters/pi/extension.tsthe bridge bootstrap was kicked off fire-and-forget at the bottom ofpiExtension(pi):For long-lived Pi sessions the race usually closes before the user types a prompt, so the bug stayed invisible. For subagents (
pi --mode json -p --no-session) the gap between extension load and the firstbefore_agent_startis too small for the spawn →initialize→tools/list→pi.registerToolround-trip, so the first (and often only) prompt of a subagent goes out with an emptyctx_*registry and the routing block (~2.5K tokens) becomes dead weight — the LLM is told to callctx_execute/ctx_search/ etc. but Pi has not yet registered them.Fix
Add a single
await _mcpBridgeReadyat the top of thebefore_agent_starthandler. The promise resolves on bootstrap success and on failure (matching the existing best-effort contract — failures are logged to stderr but never propagated), so a missing or broken bundle still cannot break agent start.Coverage added
tests/pi-extension.test.ts→Pi MCP bridge (#426) > pi-extension.ts wiring (#426 regression guard):before_agent_start awaits MCP bridge bootstrap so ctx_* are registered before LLM callregisterPiExtension, opens a session, then immediately triggersbefore_agent_start(the same race a fresh subagent process exhibits).pi.registerToolhas been called 0 times (race window confirmed open — guards against the test passing for the wrong reason if the bridge ever stops racing).ctx_*set (ctx_execute,ctx_search,ctx_index,ctx_batch_execute,ctx_fetch_and_index) has been registered throughpi.registerToolby the time the handler resolves.Verified the test fails on
nextwithout the fix (race confirmed) and passes with the fix.Test plan
npm run typecheck— passesnpx vitest run tests/pi-extension.test.ts— 47 passed (1 new + 46 existing, no flakes)npm test— full suite green (2474 passed, 25 pre-existing skips). The two[vitest-pool]: Timeout terminating forks workernotices onkiro-hooksare pre-existing onnextand unrelated to this change (16/16 tests in that file pass when run alone).npm run buildreverted before commit per CONTRIBUTING.Files touched
src/adapters/pi/extension.ts— singleawait _mcpBridgeReady+ comment explaining the subagent race.tests/pi-extension.test.ts— race regression test added to the existingpi-extension.ts wiring (#426 regression guard)block (no new test file per CONTRIBUTING).