Skip to content

fix(acp): stabilize ACP connection and surface errors to clients#29061

Open
m0n99 wants to merge 2 commits into
anomalyco:devfrom
m0n99:acp-stability
Open

fix(acp): stabilize ACP connection and surface errors to clients#29061
m0n99 wants to merge 2 commits into
anomalyco:devfrom
m0n99:acp-stability

Conversation

@m0n99
Copy link
Copy Markdown

@m0n99 m0n99 commented May 24, 2026

Issue for this PR

Closes #27779
Closes #24494

Related #26378
Related #28453
Related #24481

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Three stability fixes for the ACP agent connection layer:

1. Bind agent lifecycle to connection.signal

The Agent constructor now listens on connection.signal for abort events. When the ACP client disconnects (WebSocket close, transport error, etc.), the signal fires and the agent aborts its event subscription loop cleanly. Previously, a client disconnect left the agent's for await loop hanging indefinitely — no cleanup, no error propagation, just a ghost subscription consuming resources.

2. Surface connection errors via sessionUpdate

Added a case "session.error" branch in runEventSubscription that translates SDK error variants into ACP session/update frames with stopReason: "error". Before this, when the retry policy was exhausted, the turn would silently stall from the client's perspective: no final error notification and no stopReason. The error classification prefers structured headers (x-llm-error-type, x-llm-error-retryable, x-llm-error-reset-at, retry-after) and falls back to HTTP status-code heuristics when headers are absent.

Two session.error variants are intentionally not emitted as agent_error:

  • ContextOverflowError — compaction handles this and the turn can continue on a smaller context window.
  • MessageAbortedError — this is the normal user-stop path; the prompt RPC already reports cancellation via stopReason: "cancelled".

3. Add writeTextFile client capability

The acp CLI command now declares writeTextFile in its clientCapabilities so the server can send writeTextFile requests. Without this, the server would silently skip file-write operations.

How did you verify your code works?

  • bun test test/acp/ — 13 pass / 0 fail / 39 expect() calls
  • bun turbo typecheck — clean, 0 errors across 21 packages
  • oxlint on changed files — no new warnings
  • Test fakes updated with AbortController-based signal and closed promise to match the new constructor contract

Screenshots / recordings

N/A — backend / ACP wire protocol changes only.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels May 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions
Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Summary

No duplicate PRs found. PR #29061 appears to be unique in its combination of fixes.

There is one related PR that addresses a similar issue domain but is distinct:

@github-actions github-actions Bot removed needs:issue needs:compliance This means the issue will auto-close after 2 hours. labels May 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

…ion is ready

The AgentSideConnection constructor calls the toAgent factory before
its inner Connection is initialised, so accessing connection.signal
synchronously in the Agent constructor crashes. Wrap both the abort
listener and startEventSubscription in queueMicrotask to defer until
the Connection is available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant