feat(claude-code): use Nix-packaged pi bridge#243
Merged
Conversation
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.
Summary
This PR switches
toolsfrom the npm-installed Claude bridge to the Nix-packaged bridge fromrrvsh/pi-claude-bridge-nix. The package is consumed as a Pi local package path from the Nix store:pi-claude-bridgeflake input inflake.nix.bridge.passthru.packagePathintoprograms.pi-coding-agent.settings.packagesinnix/modules/claude-code.nix.332eb2b, which contains the confirmed runtime fixes and Cachix publication workflow.Investigation summary
The original symptom on
autowas that Claude bridge sessions often stalled after MCP/tool calls. The user-visible pattern was:Claude Code stream idle timeout after 90sdelivered 1/2, resolved 1/2, waiting=1goorkeep goingoften caused Claude to repeat the same MCP call until the missing result finally landed.Evidence from
/Users/binmohm/.pi/agent/claude-bridge-diag.logand the referenced Claude/Pi JSONL sessions showed repeatedtool_result_delivery_mismatchevents withexpectedCount: 2,deliveredCount: 1,resolvedCount: 1, andwaitingCount: 1. A recent concrete event showed Claude emitted two tool uses, Pi received and returned the first result, and the second expected tool id remained waiting until the idle timeout tore down the query.A separate build investigation found that
autocould not reachregistry.npmjs.org;prefetch-npm-depsconnections stayed inSYN_SENT. That is why the bridge package also needs CI/Cachix publication rather than relying on local npm dependency fetching onauto.Confirmed root causes
1. Active result delivery trusted only tail-extracted tool results
The bridge active-query path previously extracted tool results from the current tail and delivered those to pending MCP handlers. That missed cases where Pi history contained the result, but the result was not in the tail returned by
extractAllToolResults(context)because of interleaved messages or assistant boundaries.The Nix package patch now routes active result delivery through
deliverToolResults()inpatches/fix-multi-tool-results.patch. The helper scans full Pi context for recorded waiting ids, supplements results that are present-but-not-tail-extracted, rejects unknown ids, and skips duplicate resolved ids.Why this fix was chosen:
presentButNotExtractedIdsfrommissingFromContextIds, so future incidents reveal whether the result existed in Pi history or was genuinely absent.Alternative considered:
2. The stream-idle watchdog fired while Claude was legitimately waiting on tools
The watchdog treated “no assistant/tool output for 90s” as a retryable Claude stream stall. That is wrong while an MCP handler is still pending; Claude Code is expected to be quiet until it receives the tool result.
The bridge patch adds pending tool state to the watchdog state and suppresses monitoring while
pendingToolCallCount > 0:pendingToolCallCountpendingToolCallCount === 0pendingToolCallCount: abortCtx.pendingToolCalls.sizedoes not fire while MCP tool handlers are pendingWhy this fix was chosen:
Alternatives considered:
CLAUDE_BRIDGE_STREAM_IDLE_TIMEOUTentirely. Rejected because it would remove protection against genuine Claude stream stalls.3. Claude could emit a second
tool_usewhile Pi stream was closed, so Pi never saw itThe live test on
autoexposed a second root cause. Claude Code emitted another assistanttool_usewhile the Pi stream was already closed for a prior tool-use boundary. The bridge recorded/claimed that id internally, but the tool call was never surfaced to Pi, so Pi could not execute it and produce a result.The patch now tracks those calls as undelivered and flushes them back to Pi:
markToolCallUndeliveredQueryContext:undeliveredToolCallstoolcall_start,toolcall_end, anddone(reason=toolUse):flushUndeliveredToolCallsflushUndeliveredToolCalls(queryCtx)pendingToolCalls.size > 0branchtracks assistant tool calls that were recorded while no Pi stream was availableWhy this fix was chosen:
keep goingloop by making the missing call visible to Pi during the same active query.Alternatives considered:
Packaging and Cachix remediation
autois a restricted host that cannot reliably reachregistry.npmjs.org. The bridge package usesbuildNpmPackage, whose fixed-output dependency derivation runsprefetch-npm-depsagainst tarball URLs frompackage-lock.json. Onauto, those TCP connections timed out.The bridge repository now publishes both the npm dependency derivation and the final bridge package to Cachix:
rrvshCachix withCACHIX_AUTH_TOKENand builds bothpi-claude-bridge.npmDepsandpi-claude-bridgeforx86_64-linuxandaarch64-darwin:build.yml.checkPhase:nix/pi-claude-bridge.nix.Why this was chosen:
autosubstitute fromrrvsh.cachix.orginstead of contacting npm.Alternatives considered:
registry.npmmirror.com. This worked as a proof of remediation, but it changes the registry trust boundary and is better as a fallback than the default path.auto. Rejected because investigation proved direct npm registry access is blocked or timing out.Validation
Bridge package validation:
nix build --print-build-logs .#pi-claude-bridgepassed inrrvsh/pi-claude-bridge-nix.checkPhase:tests/unit-import.mjstests/unit-querycontext.mjstests/unit-rate-limit.mjstests/unit-tool-result-delivery.mjs2834397405028344504492Downstream/tools validation:
nix develop -c just check-nixpassed.autofetched the final aarch64-darwin bridge package for332eb2bfromhttps://rrvsh.cachix.org.Live runtime validation on
auto:CLAUDE_BRIDGE_STREAM_IDLE_TIMEOUT=5sto make the old failure easy to trigger.sleep 1; printf 'DEFER_REPRO_A\n'sleep 8; printf 'DEFER_REPRO_B\n'tool_result_delivery_mismatchdiagnostic was produced during the passing run.Caveats
rrvsh/pi-claude-bridge-nix.autobecause noninteractive SSH did not have Claude Code login state available.