Skip to content

fix(acp): classify 'Authentication required' as non-retryable auth error (#3831) - #3881

Open
iroiro147 wants to merge 2 commits into
block:mainfrom
iroiro147:fix/acp-auth-required-3831
Open

fix(acp): classify 'Authentication required' as non-retryable auth error (#3831)#3881
iroiro147 wants to merge 2 commits into
block:mainfrom
iroiro147:fix/acp-auth-required-3831

Conversation

@iroiro147

@iroiro147 iroiro147 commented Jul 31, 2026

Copy link
Copy Markdown

Fixes #3831

Problem

Expired Claude subscription credentials surface from claude-agent-acp as a bare AgentError:

Agent reported error (code -32000): Authentication required

is_auth_error() only matched the two variants PR #2751 added (Re-authenticate, API Error: 401), so this third variant fell into the generic queue.requeue() path. Every mention burned ~10 retry slots over a long backoff before dead-lettering — while presence kept the agent online and nothing told the user the agent was effectively dead. The reporter (#3831) had a container Up, socket connected, and no signal for hours after credential expiry.

Root cause

crates/buzz-acp/src/lib.rs is_auth_error() pattern-match missed the subprocess's bare-credential message.

Fix (narrow, +1 arm)

Add message.contains("Authentication required") alongside the two existing arms in is_auth_error(). Now the expired-credential case hits the existing immediate dead-letter + in-channel re-auth notice path (post_failure_notice), so each failed mention surfaces where the user is already looking.

Precision bar preserved (per the fn's doc-comment): the matched token is a subprocess credential hand, not a generic failure string. Adjacent tests prove no over-classification — transient/transport (Io, WriteTimeout) and non-auth AgentError (usage-credit) variants remain false.

Not in scope (deliberately)

  • Presence/away-after-N-errors (reporter option A) — separate, larger policy change.
  • Non-zero exit / unhealthy signal to the container runtime (option C) — separate.

Both are listed as follow-ups on #3831; this PR closes the silent-failure root for the documented message variant.

Test

  • is_auth_error_matches_authentication_required_messagefail-first (red on origin/main before the fix, green after).
  • Existing is_auth_error_* (reauthenticate / 401 / reject-non-auth / reject-transport) all pass.
  • Full cargo test -p buzz-acp --lib green (650/650 serially; 2 keepalive/idle timers are pre-existing flakes under parallel load, unrelated — pass in isolation on clean origin/main).
  • cargo clippy -p buzz-acp --lib clean.

DCO --signoff present. No changeset (repo FILE-SIZE/no-changeset convention).

Expired subscription credentials surface from claude-agent-acp as a bare
'Authentication required' AgentError (code -32000), distinct from the two
variants PR block#2751 already classified ('Re-authenticate', 'API Error: 401').
is_auth_error() missed it, so these errors fell into the generic requeue path
and burned ~10 retry slots over a long backoff before dead-lettering, while
presence showed the agent 'online' with no user-visible signal.

Add the 'Authentication required' pattern so the expired-credential case hits
the immediate dead-letter + in-channel re-auth notice path. The classifier's
precision bar is preserved: the pattern is a credential hand, not a generic
failure, and false-positive risk (silently dropping a user message) stays nil
for transient/transport errors, which remain non-AgentError and unclassified.

Fail-first test is_auth_error_matches_authentication_required_message covers
the reporter-observed message; adjacent rejection tests (usage-credit, I/O,
WriteTimeout) confirm no over-classification.

Refs block#3831

Signed-off-by: iroiro147 <sarthak.singh@juspay.in>
@Chessing234

Copy link
Copy Markdown
Contributor

treating auth-required as non-retryable is the right call for #3831. maybe also flip presence offline in the same path so the ui doesn't keep saying online.

The auth dead-letter path kept the presence heartbeat publishing 'online'
while every subsequent mention dead-letters, so the UI shows the agent as
available through a guaranteed outage. Thread the loop-scoped
(presence_publisher, presence_keys) into handle_prompt_result and, on the
non-retryable auth branch, best-effort publish kind:20001 'offline' (2s
timeout, mirrors the shutdown flip). Gated on presence_enabled; the spawned
task is non-blocking and never panics the dispatcher.

Review suggestion from Chessing234 on block/buzz PR block#3881.

Signed-off-by: iroiro147 <sarthak.singh@juspay.in>
@iroiro147

Copy link
Copy Markdown
Author

@Chessing234 Done — presence now flips offline on the same non-retryable auth path.

Expired credentials previously dead-lettered while the heartbeat kept publishing online, so the UI showed the agent available through a guaranteed outage. What's added (commit ee70a11):

  • Threaded the loop-scoped (presence_publisher, presence_keys) into handle_prompt_result.
  • On the auth branch, after spawn_failure_notice, we best-effort publish kind:20001 offline — 2s timeout, mirrors the shutdown flip, non-blocking (spawned task can't panic the dispatcher), and gated on config.presence_enabled.

So the moment the credential is known-dead, presence reflects reality instead of waiting for the agent to exit. All 22 error-emission tests still green, including auth_error_dead_letters_immediately_without_requeueing.

If you'd rather not tie this to the existing PR's classification change, happy to split it into a follow-up — but it felt tightly scoped to the same wart.

@iroiro147

Copy link
Copy Markdown
Author

Done — ee70a11f flips presence to offline (best-effort tokio spawn + 2s timeout, gated on presence_enabled) inside the non-retryable auth dead-letter branch in handle_prompt_result (crates/buzz-acp/src/lib.rs:3175), mirroring the shutdown flip at crates/buzz-acp/src/lib.rs:2705. The heartbeat no longer advertises online through a guaranteed outage. Tests stay green (30 passed on the auth filter).

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.

Agent stays "online" after its Claude credentials expire — every mention fails with -32000, nothing surfaces it

2 participants