Skip to content

feat(acp): Route A follow-ups — token usage, opt-in headless auth, status tests + bridge-auth learning#1682

Merged
gsxdsm merged 7 commits into
mainfrom
feature/acp-route-a-followups
Jun 16, 2026
Merged

feat(acp): Route A follow-ups — token usage, opt-in headless auth, status tests + bridge-auth learning#1682
gsxdsm merged 7 commits into
mainfrom
feature/acp-route-a-followups

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Route A follow-ups (after #1680 + #1681, merged): the remaining items from the post-merge backlog, now including item 1 (connection reuse, OQ2). Plus a compound learning doc capturing the hard-won bridge-auth diagnosis.

Changes

  • Compound learningdocs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md: the claude-code-cli-acp "Not logged in" failure (thin spawn env needing XDG_*/USER/SHELL, + macOS login-Keychain session isolation for headless processes) that defeated six autonomous tasks. Cross-linked from the ACP runtime pattern doc.
  • OQ3 — token usage: streamViaAcp now captures PromptResponse.usage and feeds it into the event bridge, so ACP-path turns report token usage/cost instead of always zero. Zero-when-absent is safe; tool-use (break-early) turns inherently report zero. Untrusted bridge usage is coerced to finite, non-negative numbers; cache-read/creation tokens are propagated.
  • R17 — opt-in headless auth: when FUSION_CLAUDE_ACP_FORWARD_AUTH=1, the bridge env forwards a single Claude auth token (CLAUDE_CODE_OAUTH_TOKEN > ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY) from the operator's launch env, so a detached daemon with no login Keychain can authenticate. Default OFF — the secure no-secrets posture is unchanged.
  • U12 — dashboard test coverage: GET /providers/claude-cli/status acp block + auth-failure signal are now tested.

Now included — item 1 (connection reuse / resume latency, OQ2)

Opt-in warm connection reuse, gated behind FUSION_CLAUDE_ACP_REUSE (default OFF so the default path stays functionally identical — reviewer-verified). When ON and sessionId && messages.length>1 && cache hit && cwd match && !inUse, a turn reuses the warm bridge connection + ACP session, skipping the cold bridge/claude spawn and session/new round-trip and sending only the latest-turn delta (buildResumePrompt) since the warm claude session already holds prior turns server-side. A stable router indirection lets the long-lived connection handler serve each turn's fresh state.

Hardened per an adversarial review of the reuse path:

  • P0 — fail-fast on warm-child death: the long-lived child.on("close") is bound to the cold turn that spawned it; a router.fail liveness hook routes a mid-prompt child death to the current owner turn, so a reuse turn ends immediately instead of hanging until the 30-min inactivity timeout.
  • P1 — identity-aware eviction: evictCachedAcpConn(key, entry) deletes the map key only when it still points at that entry, so a concurrent cold turn / stale close handler / idle timer can't evict or kill a newer live entry's child.
  • P1 — empty-resume guard: an empty delta cold-starts (full history) instead of issuing an empty prompt that could hang.
  • P2 — cross-turn bleed guard: a per-turn token drops stray updates once the shared warm connection moves to a newer turn.
  • The idle reaper (~5 min) is unref'd so it never pins the process.

New multi-turn tests: turn 2 reuses (no second spawn / session/new, single connection's prompt() twice), flag-off spawns fresh each turn, fail-fast on warm-child death, and empty-resume cold fallback.

Tests: pi-claude-cli 346, engine enable 6, dashboard acp 2 — all green; typecheck clean. The default claude -p path and default ACP behavior are unchanged (all additions are additive/opt-in).

Also genuinely deferred (operational, not code-fixable here)

  • OS-level sandboxing of the bridge subprocess.
  • Default-on rollout monitoring (error/latency/fallback rates).
  • A live multi-turn soak of reuse before flipping FUSION_CLAUDE_ACP_REUSE on by default.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Opt-in forwarding of exactly one ACP credential into the bridge subprocess environment
    • Token usage data (including cache read/write) is now surfaced via ACP prompting sessions
    • Optional warm bridge connection reuse for faster turn-to-turn operation
  • Documentation

    • Expanded ACP “security floor” guidance to require a complete environment allow-list
    • Added troubleshooting for Claude CLI bridge “Not logged in” caused by thin env and macOS Keychain isolation
  • Tests

    • Added/expanded ACP status endpoint integration tests, auth env rules, token usage propagation, and reuse behavior

gsxdsm and others added 4 commits June 15, 2026 13:15
…in session isolation

Compound learning: the claude-code-cli-acp bridge returned 'Not logged in'
despite a working claude -p, due to (1) a too-thin spawn env (needs XDG_*/USER/
SHELL beyond HOME/PATH) and (2) macOS login-Keychain session isolation for
detached/headless processes. Six headless tasks misdiagnosed it as an upstream
gap. Cross-linked from the ACP runtime integration pattern doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…l (U12)

GET /providers/claude-cli/status: asserts acp.{enabled,bridgeAvailable,active,
authFailed,authReason} reflect the FUSION_CLAUDE_ACP env + the bridge auth-failure
signal file, and that acp is inactive/clean when no bridge path is published.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Item 2 (OQ3): capture PromptResponse.usage from conn.prompt and feed it into
  the bridge before finish(), so ACP-path turns report token usage/cost instead
  of always zero. Zero-when-absent is safe; tool-use (break-early) turns
  inherently report zero (the prompt result never resolves).
- Item 3 (R17): opt-in headless credential delivery. When
  FUSION_CLAUDE_ACP_FORWARD_AUTH=1, buildBridgeEnv forwards a SINGLE Claude auth
  token (CLAUDE_CODE_OAUTH_TOKEN > ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY) from
  the operator's launch env so a detached daemon (no login Keychain) can
  authenticate. Default OFF — the secure no-secrets posture is unchanged.

acp-driver tests 9/9 (usage + the three auth-opt-in cases); typecheck clean.
Remaining: item 1 (connection reuse / resume latency).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 943880b9-c58a-435c-8947-8467217b7983

📥 Commits

Reviewing files that changed from the base of the PR and between b0bb39a and 65c4958.

📒 Files selected for processing (3)
  • docs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md
  • packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
  • packages/pi-claude-cli/src/acp-driver.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/pi-claude-cli/src/tests/acp-driver.test.ts
  • packages/pi-claude-cli/src/acp-driver.ts

📝 Walkthrough

Walkthrough

acp-driver.ts exports buildBridgeEnv with opt-in auth credential forwarding when FUSION_CLAUDE_ACP_FORWARD_AUTH=1. streamViaAcp captures conn.prompt results and emits token usage in a message_delta before finish("stop") on non-tool-use turns. Event-bridge updates cache token accounting during delta streaming. The transport now supports warm connection reuse across turns (FUSION_CLAUDE_ACP_REUSE) via session caching and per-turn identity tracking. Tests cover auth forwarding, token usage, connection reuse, and the dashboard ACP status endpoint. Documentation describes the macOS Keychain/thin-env root cause and prevention patterns.

Changes

ACP Bridge Auth, Token Usage, Connection Reuse, and Documentation

Layer / File(s) Summary
Auth credential forwarding opt-in
packages/pi-claude-cli/src/acp-driver.ts, packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
buildBridgeEnv is exported and extended with BRIDGE_AUTH_ENV_KEYS to forward exactly one priority-ordered, whitespace-trimmed credential (from CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY) when FUSION_CLAUDE_ACP_FORWARD_AUTH=1. Tests verify secure default (no auth forwarding), opt-in single-token forwarding, precedence rules, and strict process.env sourcing with environment restoration.
Token usage propagation in streamViaAcp and streaming deltas
packages/pi-claude-cli/src/acp-driver.ts, packages/pi-claude-cli/src/event-bridge.ts, packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
Imports buildResumePrompt for reuse path. Introduces emitUsage(res) to coerce and emit token counts into message_delta when no pi-known tool call was surfaced. Updates requestPermission handler to route tool surfaces and deny by default. Mock harness returns usage alongside stopReason. Event-bridge handleMessageDelta updates output.usage.cacheRead and output.usage.cacheWrite from Claude streaming cache token fields. Tests assert token counts propagate to done.message.usage, malformed usage is rejected, and no usage is emitted for tool-use turns.
Warm connection reuse infrastructure and cache lifecycle
packages/pi-claude-cli/src/acp-driver.ts, packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
Introduces session cache structures (AcpRouter, CachedAcpConn), Map-backed connection cache keyed by reuse key, per-turn identity tracking (myTurn, activeTurn), and evictCachedAcpConn with identity guards. Computes reuseKey based on FUSION_CLAUDE_ACP_REUSE, sessionId, and message count. Centralizes end-of-turn cleanup via endTurn(destroy) to release/keep warm connections or evict/destroy based on reuse state and cache entry identity. Updates shared handleUpdate to guard against cross-turn content bleed. Tests cover warm reuse across turns, fast failure on child death, empty-resume cold-start, non-reuse after tool-use, and flag-off behavior.
Reuse-aware ACP execution flow and ClientSideConnection routing
packages/pi-claude-cli/src/acp-driver.ts
Refactors ClientSideConnection to route sessionUpdate and requestPermission to per-turn handlers. Reworks main ACP execution with withTimeout helper and reuse-first branch: on warm reuse, selects cached connection, builds resume blocks via buildResumePrompt, repoints handlers, prompts, emits usage, and conditionally finishes. On cold path, spawns bridge, sets up router indirection, wires handlers, calls conn.newSession within timeout, optionally caches for reuse, builds system/prompt blocks, calls conn.prompt, emits usage, and finishes. Normal completion calls endTurn(false) to keep warm; failure calls endTurn(true) to evict.
ACP driver test harness for usage and reuse
packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
Adds scriptedUsage to mock harness for token-count testing. Expands imports for spawn, ClientSideConnection, and buildBridgeEnv to enable reuse and env-precedence test suites.
Dashboard status endpoint tests
packages/dashboard/src/__tests__/routes-auth.test.ts
Two new integration tests for GET /api/providers/claude-cli/status: first sets ACP env vars and writes bridge-auth signal file, asserting acp.active, acp.authFailed, and auth failure reason; second removes env var and signal file, asserting all ACP flags false. Both restore environment and clean up temporary files.
Integration-issue documentation, architecture guidance, and changeset
docs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md, docs/solutions/architecture-patterns/acp-persistent-jsonrpc-agent-runtime-integration.md, .changeset/acp-route-a-claude-cli-bridge.md
New integration-issue doc describes ACP bridge "Not logged in" failure via thin env allow-list and macOS Keychain login-session isolation. Proposes explicit, complete env allow-list, R17 runtime gate, and cross-process auth-signal file pattern. Explains compounding mechanisms and prevention guidance. Architecture-pattern doc gains cross-reference and specific required env vars. Changeset documents auth forwarding opt-in and warm-reuse feature (sessionId-keyed, turn-safe, identity-aware eviction, empty-resume cold-start, per-turn token dropping).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Runfusion/Fusion#1680: Both PRs modify packages/pi-claude-cli/src/acp-driver.ts around ACP bridge subprocess environment handling and auth gating (env allow-list plus token-forwarding behavior).
  • Runfusion/Fusion#1681: Implements the /providers/claude-cli/status endpoint and bridge auth-signal behavior infrastructure that the new dashboard integration tests exercise.

Poem

🐇 Credentials hop through BRIDGE_AUTH_ENV_KEYS so fine,
Token counts shine in the message_delta line.
Warm connections persist across turns with sessionId,
Keychain isolation fixed by the docs we see.
A rabbit sealed the thin-env breach with care—
The ACP bridge now roams secure everywhere! 🔑

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: ACP Route A follow-ups covering token usage, optional headless authentication, status tests, and bridge-auth learning documentation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/acp-route-a-followups

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR delivers four follow-up items for the ACP (Route A) transport: opt-in warm connection reuse (FUSION_CLAUDE_ACP_REUSE=1, OQ2), ACP token usage surfacing including cache fields (OQ3), opt-in headless auth forwarding (FUSION_CLAUDE_ACP_FORWARD_AUTH=1, R17), dashboard test coverage for the ACP status block, and a compound learning doc for the bridge "Not logged in" failure.

  • Connection reuse (OQ2): A sessionId-keyed cache holds live bridge connections across turns; warm turns send only the buildResumePrompt delta. The router indirection correctly routes mid-turn child death to the current owner, and the !sawToolCall guard in keepWarm ensures tool-use break turns always tear down rather than releasing a still-pending conn.prompt() as warm.
  • Token usage (OQ3) + cache fix: emitUsage sends a message_delta event after conn.prompt() resolves; handleMessageDelta in event-bridge.ts is extended to consume cache_read_input_tokens/cache_creation_input_tokens, matching handleMessageStart parity. Untrusted values are coerced via Number.isFinite && >= 0.
  • Auth forwarding (R17): buildBridgeEnv reads only from process.env (never the caller-supplied object) and forwards at most one token in priority order, with whitespace-blank values treated as absent.

Confidence Score: 5/5

Safe to merge. All new behaviour is opt-in behind env flags (default OFF), the default cold ACP path is functionally unchanged, and the reuse teardown logic correctly handles the tool-use break edge case.

The connection-reuse logic is the most complex addition, and on inspection the key hazards (tool-use break releasing a pending prompt, cross-turn bleed, stale eviction of a newer entry's child, warm-child death hanging a reuse turn) are all correctly guarded and covered by dedicated tests. The only finding is a stale file-header sentence that no longer describes the reuse path accurately.

packages/pi-claude-cli/src/acp-driver.ts — the file header comment about buildResumePrompt is now inaccurate; otherwise the implementation and tests look correct.

Important Files Changed

Filename Overview
packages/pi-claude-cli/src/acp-driver.ts Major expansion: opt-in connection reuse (OQ2), token usage surfacing (OQ3), and R17 headless auth forwarding. Core logic is well-guarded; tool-use break path correctly evicts via sawToolCall check rather than releasing warm. File header comment is now stale about buildResumePrompt.
packages/pi-claude-cli/src/event-bridge.ts Adds cache token propagation (cacheRead/cacheWrite) to handleMessageDelta, bringing it to parity with handleMessageStart for the ACP usage path. Assignment semantics prevent any double-counting risk.
packages/pi-claude-cli/src/tests/acp-driver.test.ts Adds token usage tests, multi-turn reuse tests, and full BRIDGE_AUTH_ENV_KEYS coverage including whitespace-blank token and substitution-attack vectors. beforeEach/afterEach correctly isolates auth vars.
packages/dashboard/src/tests/routes-auth.test.ts Two new integration tests for the ACP status block with proper env teardown in finally blocks.
docs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md New troubleshooting doc for the 'Not logged in' diagnosis covering thin spawn env and macOS Keychain session isolation.
docs/solutions/architecture-patterns/acp-persistent-jsonrpc-agent-runtime-integration.md One-line update to the env allow-list rule, adding a cross-link to the new Keychain-isolation doc.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[streamViaAcp called] --> B{FUSION_CLAUDE_ACP_REUSE=1
& sessionId
& messages.length > 1?}
    B -- No --> COLD[Cold path]
    B -- Yes --> C{acpSessionCache.get
reuseKey?}
    C -- Miss --> COLD
    C -- Hit --> D{warm.inUse
or cwd mismatch?}
    D -- Yes --> COLD
    D -- No --> E[buildResumePrompt]
    E --> F{Delta empty?}
    F -- Yes --> EVICT[evictCachedAcpConn
cold-start instead]
    EVICT --> COLD
    F -- No --> WARM[Reuse path
skip spawn + session/new
prompt delta only]
    WARM --> G{Tool-use break?}
    G -- Yes --> TEARDOWN[endTurn sawToolCall=true
keepWarm=false
evict + kill]
    G -- No --> RELEASE[endTurn keepWarm=true
set idle timer 5min
router handlers nulled]
    COLD --> H[spawn bridge
registerProcess
create router]
    H --> I[conn.initialize
conn.newSession]
    I --> J{reuseKey set?}
    J -- Yes --> CACHE[acpSessionCache.set]
    J -- No --> PROMPT
    CACHE --> PROMPT[conn.prompt full history]
    PROMPT --> K{Tool-use break?}
    K -- Yes --> TEARDOWN
    K -- No --> L[emitUsage
finish stop]
    L --> RELEASE
    RELEASE --> IDLE[(Warm connection idle in cache)]
    IDLE -- Next turn same sessionId --> C
    IDLE -- Idle timeout 5min --> EVICT2[evictCachedAcpConn kill child]
    IDLE -- Child dies --> EVICT2
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[streamViaAcp called] --> B{FUSION_CLAUDE_ACP_REUSE=1
& sessionId
& messages.length > 1?}
    B -- No --> COLD[Cold path]
    B -- Yes --> C{acpSessionCache.get
reuseKey?}
    C -- Miss --> COLD
    C -- Hit --> D{warm.inUse
or cwd mismatch?}
    D -- Yes --> COLD
    D -- No --> E[buildResumePrompt]
    E --> F{Delta empty?}
    F -- Yes --> EVICT[evictCachedAcpConn
cold-start instead]
    EVICT --> COLD
    F -- No --> WARM[Reuse path
skip spawn + session/new
prompt delta only]
    WARM --> G{Tool-use break?}
    G -- Yes --> TEARDOWN[endTurn sawToolCall=true
keepWarm=false
evict + kill]
    G -- No --> RELEASE[endTurn keepWarm=true
set idle timer 5min
router handlers nulled]
    COLD --> H[spawn bridge
registerProcess
create router]
    H --> I[conn.initialize
conn.newSession]
    I --> J{reuseKey set?}
    J -- Yes --> CACHE[acpSessionCache.set]
    J -- No --> PROMPT
    CACHE --> PROMPT[conn.prompt full history]
    PROMPT --> K{Tool-use break?}
    K -- Yes --> TEARDOWN
    K -- No --> L[emitUsage
finish stop]
    L --> RELEASE
    RELEASE --> IDLE[(Warm connection idle in cache)]
    IDLE -- Next turn same sessionId --> C
    IDLE -- Idle timeout 5min --> EVICT2[evictCachedAcpConn kill child]
    IDLE -- Child dies --> EVICT2
Loading

Reviews (3): Last reviewed commit: "fix(review): address PR #1682 re-review ..." | Re-trigger Greptile

Comment thread packages/pi-claude-cli/src/acp-driver.ts Outdated
Comment thread packages/pi-claude-cli/src/__tests__/acp-driver.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@docs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md`:
- Around line 43-44: The documentation at lines 43-44 states "never API keys"
when describing the secure default behavior for environment variable forwarding
to the bridge subprocess, but does not acknowledge the opt-in exception that
allows forwarding exactly one auth credential when
FUSION_CLAUDE_ACP_FORWARD_AUTH=1 is set in the implementation. Update the
wording in both the main section (lines 43-44) and the related section (lines
79-83) to explicitly distinguish between the secure default behavior (no
credentials forwarded) and the explicit opt-in mechanism
(FUSION_CLAUDE_ACP_FORWARD_AUTH=1 environment variable). This clarification will
align the documentation with the actual implementation behavior.

In `@packages/pi-claude-cli/src/__tests__/acp-driver.test.ts`:
- Around line 173-176: The auth-forwarding tests are not properly isolating and
restoring the ANTHROPIC_AUTH_TOKEN environment variable, which can cause test
interference and leave an important surface unasserted. At the anchor site in
acp-driver.test.ts lines 173-176, add ANTHROPIC_AUTH_TOKEN to the saved object
alongside FUSION_CLAUDE_ACP_FORWARD_AUTH, CLAUDE_CODE_OAUTH_TOKEN, and
ANTHROPIC_API_KEY, then include it in the restoration logic within the afterEach
hook. Apply the same change pattern at the sibling sites in acp-driver.test.ts
lines 188-194 and 196-203 where similar test setup and teardown occurs, ensuring
all three locations capture, save, and restore ANTHROPIC_AUTH_TOKEN
consistently.

In `@packages/pi-claude-cli/src/acp-driver.ts`:
- Around line 143-147: The condition checking `v.length > 0` in the for loop
iterating over BRIDGE_AUTH_ENV_KEYS accepts whitespace-only strings as valid
authentication values, which can incorrectly prioritize an invalid
higher-preference token over valid lower-preference credentials. Modify the
condition to trim whitespace from the value and check that the trimmed result
has length greater than 0 before assigning it to env[key], ensuring
whitespace-only strings are treated as absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c3d984e-735d-4774-91f2-2506c744b21d

📥 Commits

Reviewing files that changed from the base of the PR and between 1b5b870 and 4e2a887.

📒 Files selected for processing (5)
  • docs/solutions/architecture-patterns/acp-persistent-jsonrpc-agent-runtime-integration.md
  • docs/solutions/integration-issues/acp-bridge-not-logged-in-thin-env-keychain-isolation.md
  • packages/dashboard/src/__tests__/routes-auth.test.ts
  • packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
  • packages/pi-claude-cli/src/acp-driver.ts

Comment thread packages/pi-claude-cli/src/__tests__/acp-driver.test.ts Outdated
Comment thread packages/pi-claude-cli/src/acp-driver.ts
gsxdsm and others added 2 commits June 15, 2026 14:19
…he tokens)

- P2: event-bridge handleMessageDelta now consumes cache_read/cache_creation
  tokens (parity with handleMessageStart) — the OQ3 usage path carried them but
  they were silently dropped, understating cost for cached turns.
- P2: validate the untrusted bridge usage payload — coerce each field to a
  finite, non-negative number before forwarding, so a malformed value
  (string/NaN/negative) can't corrupt totalTokens/cost.
- Tests: usage now asserts cache tokens + totalTokens; new cases for malformed
  usage, tool-use turns reporting zero usage, the ANTHROPIC_AUTH_TOKEN middle
  precedence, and that the auth token is read from process.env (never a
  caller-supplied value — no token substitution).
- Doc: state the auth-forwarding exposure trade-off in the code comment.

acp-driver 13/13; event-bridge tests green; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep a warm bridge connection + ACP session across turns of one
conversation (gated by FUSION_CLAUDE_ACP_REUSE=1, default OFF), so
multi-turn lanes skip the cold bridge/claude spawn and session/new
round-trip and send only the latest-turn delta (buildResumePrompt).
A stable router indirection serves each turn's handlers.

Addresses the adversarial review of the reuse path:
- P0: a warm-child death routes failure to the CURRENT owner turn via
  router.fail, so a reuse turn fails fast instead of hanging until the
  30-min inactivity timeout.
- P1: eviction is cache-identity-aware (evictCachedAcpConn only deletes
  the map key when it still points at the entry), so a concurrent cold
  turn / stale close handler / idle timer can't evict or kill a newer
  live entry's child.
- P1: an empty resume delta cold-starts instead of issuing an empty
  prompt that could hang.
- P2: a per-turn token drops cross-turn stray updates on the shared
  warm connection.
- The idle reaper is unref'd so it never pins the process.

Default OFF → the cold path is functionally unchanged (reviewer-verified).
Adds multi-turn tests: reuse skips spawn+session/new, flag-off spawns
fresh, fail-fast on warm-child death, empty-resume cold fallback.
346/346 pass, tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/pi-claude-cli/src/__tests__/acp-driver.test.ts (1)

346-352: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear ANTHROPIC_AUTH_TOKEN in this case to keep precedence deterministic.

This test expects API-key forwarding, but it can fail when ambient process.env.ANTHROPIC_AUTH_TOKEN is set. Add delete process.env.ANTHROPIC_AUTH_TOKEN; in this test setup.

As per coding guidelines, “the regression test must assert the general invariant across ALL known surfaces — not only the single reported reproduction.”

Proposed fix
   it("forwards a single auth token when opted in", () => {
     process.env.FUSION_CLAUDE_ACP_FORWARD_AUTH = "1";
     delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
+    delete process.env.ANTHROPIC_AUTH_TOKEN;
     process.env.ANTHROPIC_API_KEY = "sk-secret";
     const env = buildBridgeEnv({ HOME: "/h", PATH: "/b" });
     expect(env.ANTHROPIC_API_KEY).toBe("sk-secret");
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pi-claude-cli/src/__tests__/acp-driver.test.ts` around lines 346 -
352, The test function "forwards a single auth token when opted in" deletes
CLAUDE_CODE_OAUTH_TOKEN and sets ANTHROPIC_API_KEY, but does not delete
ANTHROPIC_AUTH_TOKEN from the environment. This can cause the test to fail
non-deterministically if ANTHROPIC_AUTH_TOKEN is set in the ambient environment,
since token precedence becomes unpredictable. Add a delete statement for
process.env.ANTHROPIC_AUTH_TOKEN in the test setup, right after the other
environment variable manipulations, to ensure the test behavior is deterministic
regardless of the ambient environment state.

Source: Coding guidelines

🧹 Nitpick comments (1)
packages/pi-claude-cli/src/__tests__/acp-driver.test.ts (1)

77-110: Use fake timers in beforeEach instead of real-time flush() waits.

The tests currently depend on a 30ms setTimeout to allow event-loop settlement before polling stream._events. This pattern increases flakiness and suite runtime. Replace with vi.useFakeTimers() in beforeEach, then call vi.runAllTimersAsync() or vi.advanceTimersToNextTimerAsync() to deterministically advance through pending callbacks.

Per coding guidelines: "Prefer fake timers over real polling/time waits. Do not mask slowness by raising worker/concurrency knobs."

Existing precedent: packages/pi-claude-cli/src/__tests__/process-manager.test.ts and provider.test.ts already use this pattern with vi.useFakeTimers() in beforeEach / vi.useRealTimers() in afterEach.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pi-claude-cli/src/__tests__/acp-driver.test.ts` around lines 77 -
110, Replace the real-time setTimeout-based flush() pattern in the
acp-driver.test.ts tests with Vitest fake timers to improve determinism and
reduce flakiness. Add vi.useFakeTimers() to a beforeEach hook and
vi.useRealTimers() to an afterEach hook. In each test function (feeds ACP token
usage, ignores a malformed/untrusted usage payload, and does not emit usage on a
tool-use turn), replace the await flush() call with vi.runAllTimersAsync() to
deterministically advance through pending callbacks instead of relying on
real-time delays. Follow the existing pattern already implemented in
process-manager.test.ts and provider.test.ts in the same package.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/pi-claude-cli/src/__tests__/acp-driver.test.ts`:
- Around line 346-352: The test function "forwards a single auth token when
opted in" deletes CLAUDE_CODE_OAUTH_TOKEN and sets ANTHROPIC_API_KEY, but does
not delete ANTHROPIC_AUTH_TOKEN from the environment. This can cause the test to
fail non-deterministically if ANTHROPIC_AUTH_TOKEN is set in the ambient
environment, since token precedence becomes unpredictable. Add a delete
statement for process.env.ANTHROPIC_AUTH_TOKEN in the test setup, right after
the other environment variable manipulations, to ensure the test behavior is
deterministic regardless of the ambient environment state.

---

Nitpick comments:
In `@packages/pi-claude-cli/src/__tests__/acp-driver.test.ts`:
- Around line 77-110: Replace the real-time setTimeout-based flush() pattern in
the acp-driver.test.ts tests with Vitest fake timers to improve determinism and
reduce flakiness. Add vi.useFakeTimers() to a beforeEach hook and
vi.useRealTimers() to an afterEach hook. In each test function (feeds ACP token
usage, ignores a malformed/untrusted usage payload, and does not emit usage on a
tool-use turn), replace the await flush() call with vi.runAllTimersAsync() to
deterministically advance through pending callbacks instead of relying on
real-time delays. Follow the existing pattern already implemented in
process-manager.test.ts and provider.test.ts in the same package.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e2b4480-1281-4f95-9767-e2c0f3128d89

📥 Commits

Reviewing files that changed from the base of the PR and between 4e2a887 and b0bb39a.

📒 Files selected for processing (4)
  • .changeset/acp-route-a-claude-cli-bridge.md
  • packages/pi-claude-cli/src/__tests__/acp-driver.test.ts
  • packages/pi-claude-cli/src/acp-driver.ts
  • packages/pi-claude-cli/src/event-bridge.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/acp-route-a-claude-cli-bridge.md

Comment thread packages/pi-claude-cli/src/acp-driver.ts
…dening)

- P1 (Greptile): a tool-use break-early turn released the warm connection
  (inUse=false) while conn.prompt() was still pending, letting the next turn
  launch a concurrent prompt on the same ACP session (protocol corruption).
  keepWarm now requires !sawToolCall, so a tool-use turn tears the connection
  down like the non-reuse path; only a clean stop turn (prompt fully resolved
  before finish) keeps it warm. + test.
- buildBridgeEnv: treat a whitespace-only auth var as absent (v.trim()), so a
  blank higher-preference token can't shadow a real lower-preference one and we
  never forward a useless blank token. + test.
- Auth-forwarding tests: clear ambient auth vars in beforeEach so a runner-env
  token can't shadow the case under test (CodeRabbit).
- Doc: clarify the allow-list never carries API keys by default; the single
  FUSION_CLAUDE_ACP_FORWARD_AUTH opt-in (default OFF) is the only exception.

348/348 pass, tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@stage-review

stage-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Document bridge auth issues and env requirements
2 Implement opt-in headless auth forwarding
3 Capture and propagate ACP token usage
4 Implement opt-in warm connection reuse
5 Verify dashboard status and changeset
Open in Stage

Chapters generated by Stage for commit 65c4958 on Jun 16, 2026 9:20am UTC.

@gsxdsm gsxdsm merged commit b85d02a into main Jun 16, 2026
6 checks passed
@gsxdsm gsxdsm deleted the feature/acp-route-a-followups branch June 16, 2026 22:12
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