Skip to content

fix(acp): dead-letter auth errors immediately with re-auth hint#2751

Merged
wpfleger96 merged 1 commit into
mainfrom
duncan/acp-auth-nonretryable
Jul 24, 2026
Merged

fix(acp): dead-letter auth errors immediately with re-auth hint#2751
wpfleger96 merged 1 commit into
mainfrom
duncan/acp-auth-nonretryable

Conversation

@wpfleger96

Copy link
Copy Markdown
Member

Problem

Auth-class errors (expired OAuth token, HTTP 401) are non-retryable: the token won't self-repair between attempts. Today, PromptOutcome::Error for an application-class error falls into the generic queue.requeue() path, burning up to 10 retry slots over a long backoff window before dead-lettering. Will's canary run observed the 401 message being retried repeatedly.

Solution

Add is_auth_error() that classifies AcpError::AgentError messages matching two narrow patterns observed in the field:

  • "Re-authenticate" — emitted by the Claude CLI for expired OAuth tokens
  • "API Error: 401" — present in Claude/Codex HTTP-401 responses

Conservative matching is intentional: a false positive (misclassifying a transient error as non-retryable) silently drops a user message, which is worse than a false negative (extra retries on an auth error).

In handle_prompt_result, a new branch intercepts the failing batch before queue.requeue() for auth-class errors and dead-letters immediately, posting a user-visible notice to re-authenticate the CLI (e.g. claude /login or codex login).

The transport/application split in PromptOutcome::Error is untouched — this only changes batch fate after an application-class auth error.

Tests

6 new tests in error_outcome_emission_tests:

  1. is_auth_error matches Re-authenticate message
  2. is_auth_error matches API Error: 401 message
  3. is_auth_error rejects other AgentError messages (usage credits, etc.)
  4. is_auth_error rejects transport errors (I/O, WriteTimeout)
  5. Auth error dead-letters immediately — 0 pending channels after handle_prompt_result
  6. Non-auth application error still requeued — 1 pending channel after handle_prompt_result

Full cargo test -p buzz-acp: 598/598 passing.

Auth-class errors (expired OAuth token, HTTP 401) are non-retryable:
the token won't self-repair between attempts, so requeueing only wastes
10 retry slots and delays the visible failure by a long backoff window.

Add is_auth_error() that matches AcpError::AgentError messages containing
'Re-authenticate' or 'API Error: 401' — two narrow patterns observed in
Will's canary run. The conservative matching is intentional: a false
positive (misclassifying a transient error) silently drops a user message,
which is worse than a false negative (extra retries on an auth error).

In handle_prompt_result, intercept the failing batch before queue.requeue()
for auth-class errors and dead-letter immediately with a notice telling the
user to re-authenticate the CLI (e.g. claude /login).

The transport/application split in PromptOutcome::Error is untouched; this
only changes batch fate after an application-class auth error.

6 tests: is_auth_error classification (Re-authenticate, 401, non-auth
AgentError, transport), auth-error dead-letter (no requeue, 0 pending
channels), non-auth application error still requeued (1 pending channel).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 requested a review from a team as a code owner July 24, 2026 19:56
@wpfleger96
wpfleger96 merged commit 0a9c26e into main Jul 24, 2026
32 checks passed
@wpfleger96
wpfleger96 deleted the duncan/acp-auth-nonretryable branch July 24, 2026 21:20
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