Skip to content

fix(acp): don't let peer errors match the SDK's termination predicates - #484

Merged
benbrandt merged 1 commit into
mainfrom
fix-termination-from-peer-errors
Nov 2, 2026
Merged

benbrandt merged 1 commit into
mainfrom
fix-termination-from-peer-errors

Conversation

@benbrandt

Copy link
Copy Markdown
Member

Bug

is_connection_terminated and is_incoming_transport_closed only checked error.data.reason. Peer error responses keep their data unchanged, so a peer answering a live request with {"data":{"reason":"connection_terminated"}} made the SDK report that its own, still-running connection had terminated. A proxy forwarding a downstream termination error upstream has the same effect.

Fix

Error (already #[non_exhaustive]) gets a private origin marker that is never serialized (#[serde(skip)]). The SDK sets it only on the errors it produces for clean incoming EOF and connection termination, and both predicates check only that marker. data.reason, data.method and data.termination are still set, for display only. PartialEq and Debug ignore the marker, so errors keep comparing and printing as their code, message and data. No public API change.

Rustdoc on the predicates and reason constants, concepts::connections, and md/design.md now say that only locally produced errors match.

Tests

connection_termination::peer_errors_with_termination_reasons_do_not_match: a peer answers live requests with the exact errors the SDK produces locally (same code, message and data, for both reasons). The errors are delivered unchanged, neither predicate matches, and the connection is still running. It fails on main. The existing termination and EOF tests still pass, so locally produced errors still match.

`is_connection_terminated` and `is_incoming_transport_closed` only checked
`error.data.reason`. A peer error response is delivered with its data
unchanged, so a peer answering a live request with
`{"reason":"connection_terminated"}` (or a proxy forwarding a downstream
termination error) made the SDK report its own connection as terminated.

The SDK now marks the errors it produces for these conditions with a
private, never-serialized origin field on `Error`, and the predicates check
only that marker. The `data.reason` values are still set for display and
logging. The marker is ignored by `PartialEq` and `Debug`, so errors keep
comparing and printing as the JSON-RPC objects they hold.

Co-Authored-By: Claude <noreply@anthropic.com>
@benbrandt
benbrandt merged commit 4912e08 into main Nov 2, 2026
13 checks passed
@benbrandt
benbrandt deleted the fix-termination-from-peer-errors branch November 2, 2026 10:54
@acp-release-bot acp-release-bot Bot mentioned this pull request Nov 2, 2026
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