Skip to content

fix(claude-local): classify an unrefreshable OAuth session as auth required - #11073

Open
juancarlosrial76-code wants to merge 1 commit into
paperclipai:masterfrom
juancarlosrial76-code:classify-oauth-session-expired
Open

fix(claude-local): classify an unrefreshable OAuth session as auth required#11073
juancarlosrial76-code wants to merge 1 commit into
paperclipai:masterfrom
juancarlosrial76-code:classify-oauth-session-expired

Conversation

@juancarlosrial76-code

@juancarlosrial76-code juancarlosrial76-code commented Aug 8, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • Agent runs go through adapters; claude-local shells out to the Claude CLI and classifies each run's outcome into an errorCode so the host can react (retry, wait for quota, prompt a login)
  • When the CLI's OAuth session dies it prints Failed to authenticate: OAuth session expired and could not be refreshed, which matches none of the auth, quota, or transient patterns
  • The run therefore lands with no classification at all and fails hard, so the host never surfaces the login path and just keeps waking the agent
  • Measured over one host's full run-log corpus this was the single largest failure bucket — 2811 of 8179 failed runs, across all 11 agents, in one continuous ~79h window
  • This pull request teaches CLAUDE_AUTH_REQUIRED_RE that wording, anchored to the CLI's whole failure sentence
  • The benefit is that an expired session resolves to claude_auth_required and its login path instead of burning runs silently

Linked Issues or Issue Description

No existing GitHub issue found — describing the bug in-PR.

What happened: every run on a host failed with
Failed to authenticate: OAuth session expired and could not be refreshed, and each one was
recorded as an unclassified hard failure rather than claude_auth_required.

Expected: an expired/unrefreshable OAuth session is an auth problem, so
detectClaudeLoginRequired should report requiresLogin: true and the run should resolve to
claude_auth_required.

Why the current code misses itCLAUDE_AUTH_REQUIRED_RE in
packages/adapters/claude-local/src/server/parse.ts has no branch that this text can hit:

  • unauthorized — not present in the text
  • authentication\s+required — the CLI says "Failed to authenticate", not "required"
  • invalid\s+api\s+key… — this is an OAuth session, not an API key

With requiresLogin false and neither CLAUDE_TRANSIENT_UPSTREAM_RE nor
CLAUDE_PROVIDER_QUOTA_RE matching, nothing classifies the run.

Impact, measured over one host's run logs (12,098 run logs → 11,046 result events →
8,179 failures). This bucket is larger than every quota wording combined:

Runs result text
2811 Failed to authenticate: OAuth session expired and could not be refreshed
2728 You've hit your weekly limit · resets …
619 Prompt is too long

The 2811 cases fall on all 11 agents of that host inside one continuous ~79h window, with
failures in 82 distinct hours — one shared host credential expiring, not one agent with a stale
login. Because nothing classified it, all 11 agents kept being woken and kept burning runs for
the entire window.

Dedup search: searched the open PR list for claude_auth_required / auth-classification
work and checked each candidate's current diff (not just its title) for this wording —
#10592, #8028, #5673, #9933 and #11053. None of them touches OAuth session expired; the
phrase appears nowhere in master today. Refs #10592, Refs #8028, Refs #5673 — those three fix
a different, adjacent bug (see Risks).

What Changed

  • parse.ts: added one branch to CLAUDE_AUTH_REQUIRED_RE matching the CLI's whole failure
    sentence, failed to authenticate … oauth session expired and could not be refreshed, with a
    comment explaining why it is anchored rather than matching the short phrase.
  • parse.test.ts: two tests pinning both directions — the CLI's failure sentence classifies as
    auth-required, and a run that merely reports on an OAuth outage does not.

Verification

cd packages/adapters/claude-local
npx vitest run src/server/          # 7 files, 83 tests passed
  • Blind probe: reverting just the regex branch makes the new
    "classifies an unrefreshable OAuth session" test fail (1 failed | 40 passed), so the test is
    not vacuously green.
  • Counter-checked each candidate pattern over the same corpus, matching the way
    detectClaudeLoginRequired does (per line of the run's result text). The function reads the
    run's own result text, and agents that report on an outage quote the CLI error back, so the
    short form would misread those successful reports as auth failures:
Pattern Catches (of 2811) Newly matched successful runs
today's regex 0 — (2 pre-existing)
+ oauth\s+session\s+expired 2811 5
+ failed\s+to\s+authenticate…oauth\s+session\s+expired 2811 1
this PR (full sentence anchored) 2811 0

The anchored form catches every real case and adds no new false positive.

Risks

Low risk. The change is additive — one alternation branch inside an existing regex — so no
previously-matching input stops matching, and the new branch is narrow enough that it did not
newly match any of the 2,870 successful runs in the corpus.

One adjacent issue this PR deliberately does not touch: in execute.ts,
loginMeta.requiresLogin is the only branch of resolvedErrorCode not gated on failed, so a
successful run whose text matches this regex is still labelled claude_auth_required. That is
pre-existing and already addressed by #10592, #8028 and #5673, so fixing it here would duplicate
their work and make this PR two concerns. The anchored wording was chosen specifically so this
change adds nothing to that exposure (0 newly-matched successful runs above).

Model Used

Claude Opus 5 (claude-opus-5[1m]), 1M context window, extended thinking, with tool use and
code execution (measurement scripts run locally over the run-log corpus).

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have not referenced internal/instance-local Paperclip issues or links (only public GitHub #NNN / github.com/paperclipai/paperclip URLs)
  • My branch name describes the change (e.g. docs/..., fix/...) and contains no internal Paperclip ticket id or instance-derived details
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • I have updated relevant documentation to reflect my changes — n/a, no user-facing docs cover this classifier
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — 29 pass, 1 skipping, 0 fail
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

@commitperclip

commitperclip Bot commented Aug 8, 2026

Copy link
Copy Markdown

✅ All checks passing — ready for Greptile review and maintainer approval.

— commitperclip

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR narrowly extends the Claude Local authentication classifier to recognize an expired OAuth session that cannot be refreshed.

  • Adds a complete-sentence OAuth authentication-failure pattern.
  • Adds positive and negative classifier tests to constrain matching.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/adapters/claude-local/src/server/parse.ts Extends the existing authentication-required regex with the Claude CLI’s unrefreshable OAuth-session failure sentence.
packages/adapters/claude-local/src/server/parse.test.ts Adds coverage for the new OAuth failure classification and a nonmatching outage-report case.

Reviews (2): Last reviewed commit: "fix(claude-local): classify an unrefresh..." | Re-trigger Greptile

…quired

The Claude CLI reports a dead OAuth session as

    Failed to authenticate: OAuth session expired and could not be refreshed

which matches none of the branches in `CLAUDE_AUTH_REQUIRED_RE` — not
`unauthorized`, not `authentication required` (the CLI says "Failed to
authenticate"), and it is an OAuth session rather than an API key. With
`detectClaudeLoginRequired` returning false and neither the transient-upstream
nor the provider-quota pattern matching either, the run ends up unclassified
and fails hard instead of surfacing `claude_auth_required` and its login path.

On one host's run-log corpus this was the single largest error bucket: 2811 of
8179 failed runs, spread over every one of the 11 agents in a continuous ~79h
window — a shared host credential expiring, not a per-agent problem. Because
nothing classified it, all 11 agents kept being woken and kept burning runs for
the entire window.

Match the CLI's whole failure sentence rather than `OAuth session expired`
alone. `detectClaudeLoginRequired` reads the run's own `result` text, and agents
that *report on* an outage quote that phrase back; counter-checking both forms
over the same corpus, the short form newly matched 5 successful runs while the
anchored form matched none.
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.

2 participants