Skip to content

fix: gate CLI background usage fetch on confirmed-absent OAuth creds - #2813

Merged
steipete merged 7 commits into
steipete:mainfrom
axisrow:upstream-pr-2708
Aug 13, 2026
Merged

fix: gate CLI background usage fetch on confirmed-absent OAuth creds#2813
steipete merged 7 commits into
steipete:mainfrom
axisrow:upstream-pr-2708

Conversation

@axisrow

@axisrow axisrow commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes two independent causes of the intermittent "Claude OAuth credentials not found. Run claude to authenticate." flicker (#2708), both of which surface the same misleading error while the user is actually still authenticated:

1. CLI-background deadlock (original scope). After a signed-out/failed background Claude CLI attempt, the background Auto usage-fetch path could stay permanently blocked (or, depending on the exact failure sequence, get silently re-permitted) because the OAuth-absence deadlock-breaker did not consistently account for revoked or unidentified profiles. A fresh sign-in would work briefly and then the provider would fall back to "No available fetch strategy" again.

  • Add directCredentialIsMissingOverride TaskLocal to ClaudeOAuthFetchStrategy for deterministic test overrides.
  • Thread an oauthCredentialsConfirmedAbsent check into ClaudeCLIBackgroundAvailability.allowsBackgroundAutoUsageFetch so the CLI background fallback only fires when direct OAuth credentials are confirmed absent.
  • Require a non-nil, non-revoked background-availability marker before consulting the OAuth-absence fallback, so:
    • a marker revoked by a failed foreground fetch is not re-permitted on the next background tick (must wait for the next successful foreground fetch), and
    • a profile CodexBar cannot identify (missing/malformed/unreadable account config) never reaches the fallback at all, since a failed attempt for such a profile could never be recorded as a revocation.

2. Background web-cookie recovery (added in this update). ClaudeWebAPIFetcher.fetchUsageSerialized — the Claude web/cookie usage path — clears a stale cached session cookie on an auth failure and unconditionally falls through to a browser cookie read to recover, the exact same shape as the bug fixed for Ollama in #2814 (ollama.com's equivalent report was #2072). In a background context BrowserCookieAccessGate typically denies that read (no interactive Keychain prompt outside a user-initiated action), so the browser lookup finds nothing and a generic "no session key found" error replaces the original, more informative cached-auth error (surfacing as this same flicker). Fixed with the identical pattern used for Ollama:

  • Still attempt browser-cookie recovery after clearing the stale cache, even in a background context — BrowserCookieAccessGate already gates that read on its own no-UI preflight (Safari never needs Keychain decryption, and a Chromium browser with a prior "Always Allow" Keychain grant is read without a prompt), so a background attempt is not unconditionally blocked.
  • Only when that recovery attempt also fails do we now surface the original cached-auth error instead of the misleading generic one.
  • Added ClaudeWebBackgroundRecoveryTests covering: background recovery that finds nothing (original error surfaces), background recovery that succeeds without a prompt (still works, proving the fix doesn't block legitimate no-UI recovery), and the equivalent user-initiated case.

Reviewed over 3 local review cycles (/review + a Codex adversarial-review companion) before opening; two real logic gaps were found and fixed, remaining findings were verified as either duplicates of already-accepted deliberate behavior or non-issues.

Closes #2708

Test plan

  • swift test --filter ClaudeCLIBackgroundAvailabilityTests (14/14 passing)
  • swift test --filter ClaudeWebBackgroundRecoveryTests (3/3 passing)
  • swift test --filter ClaudeWebCookieRenewalTests (15/15 passing, no regressions)
  • make lint (0 violations)
  • Full swift test suite (large parallel run is flaky on this machine due to unrelated timing-sensitive tests under CPU load — e.g. AdaptiveRefreshTimerTests, ClaudeOAuthCredentialsStoreNeverPromptCacheTests' subprocess-based tombstone tests; verified these same suites fail identically on the pre-fix commit, i.e. unrelated to this change. All suites touched by this PR pass cleanly in isolation.)

🤖 Generated with Claude Code

)

* fix: gate CLI background usage fetch on confirmed-absent OAuth creds

Add directCredentialIsMissingOverride TaskLocal for deterministic testing
and thread an oauthCredentialsConfirmedAbsent check into
ClaudeCLIBackgroundAvailability.allowsBackgroundAutoUsageFetch so the CLI
background fallback only proceeds when direct OAuth credentials are
confirmed absent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HqBGFKF9M8etkRpgz2bk2Z

* fix: honor CLI background revocation before the OAuth absence fallback

allowsBackgroundAutoUsageFetch's deadlock-breaker only checked
isEstablished(), which drops a marker as soon as it's revoked by a
failed foreground fetch. That let the OAuth-absence probe re-permit
a background CLI usage attempt on every tick after a failure,
defeating the existing revocation/backoff guarantee. Check
isRevoked() before falling through to oauthCredentialsConfirmedAbsent
so a revoked marker stays denied until the next foreground success.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HqBGFKF9M8etkRpgz2bk2Z

* fix: require an identified profile before the OAuth absence fallback

allowsBackgroundAutoUsageFetch's deadlock-breaker could still fire for
a profile ClaudeAccountProfile.identifiedSessionScope can't identify
(missing/malformed/unreadable account config), since isEstablished and
the round-1 isRevoked check both silently return false when
captureMarker is nil. That let a background CLI fetch launch with no
stable account binding, and a failed attempt could never be recorded
as a revocation (revoke() needs a marker), so nothing would bound
repeated launches. Require a non-nil marker before consulting
oauthCredentialsConfirmedAbsent, matching the fail-closed contract
identifiedSessionScope already documents for background work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HqBGFKF9M8etkRpgz2bk2Z

---------

Co-authored-by: axisrow <axisrow@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@clawsweeper

clawsweeper Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 907644516e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// is durably dead: it is only recorded by a prior *successful* user-initiated CLI fetch, and a
// scheduled refresh never reaches user-initiated status. Breaking that deadlock here mirrors
// explicit OAuth mode's own absence check (`ClaudeOAuthPlanningAvailability`).
return oauthCredentialsConfirmedAbsent()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep cold CLI fallback behind user initiation

When a scheduled Auto refresh has an identifiable profile but no local OAuth cache/file, this return bypasses the existing foreground-establishment and .always prompt-policy gates. ClaudeCLIFetchStrategy.fetch then uses the .cli path, which goes directly to the interactive PTY without ClaudeCLIAuthStatusProbe; for a logged-out CLI this can open browser OAuth, and the opaque child can also surface Keychain UI, despite the refresh being in the background and the default policy being onlyOnUserAction. A confirmed absence of CodexBar-readable credentials does not establish that the interactive CLI is safe to launch unattended, so this exception should still require explicit background opt-in or a noninteractive logged-in preflight.

Useful? React with 👍 / 👎.

…back

Address Codex's P1 review finding on the upstream PR: a confirmed
absence of CodexBar-readable OAuth credentials does not by itself
prove the interactive Claude CLI is safe to launch unattended in the
background. The deadlock-breaker in allowsBackgroundAutoUsageFetch now
requires the same explicit background opt-in (.always prompt policy)
that allowsOpaqueChildExecution already requires, instead of relying
solely on the credential-absence probe. Update tests to cover both the
opted-in and not-opted-in cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HqBGFKF9M8etkRpgz2bk2Z
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. labels Aug 9, 2026
@clawsweeper

clawsweeper Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed August 12, 2026, 11:57 AM ET / 15:57 UTC.

ClawSweeper review

What this changes

The PR restricts unattended Claude CLI fallback to confirmed absent OAuth credentials and retains a prior cached-session error when browser-cookie recovery cannot replace it.

Merge readiness

Blocked until real behavior proof from a real setup is added - 6 items remain

Keep open: current main and v0.49.2 still lack the proposed Claude fallback behavior, but the PR continues to mask errors from a successfully discovered replacement browser session. Likely related people: Peter Steinberger and pickaxe (medium confidence).

Priority: P1
Reviewed head: 401d9f2505125ceef0679738f36bc4ce7b6c81e2
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🧂 unranked krab (1/6) The patch has focused coverage but retains a concrete error-propagation defect and lacks real behavior proof.
Proof confidence 🧂 unranked krab (1/6) Needs real behavior proof before merge: The PR reports focused tests and lint, but provides no redacted after-fix trace from a real Claude background CLI or browser-cookie recovery path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦐 gold shrimp (3/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: The PR reports focused tests and lint, but provides no redacted after-fix trace from a real Claude background CLI or browser-cookie recovery path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 4 items Current main and release still need this work: Current main and v0.49.2 call the background CLI gate with only binary and environment, so neither includes the PR’s confirmed-absence fallback.
Replacement-session errors are masked: The changed catch covers both cookie discovery and the subsequent fetch using a discovered replacement session, replacing either failure with the stale cached-session error.
Prior blocker remains at the same head: The previous ClawSweeper review recorded this P2 error-propagation concern at the same current PR head; no subsequent commit addresses it.
Findings 1 actionable finding [P2] Propagate replacement-session fetch failures
Security None None.

How this fits together

CodexBar’s Claude provider selects OAuth, CLI, and browser-cookie sources for manual and scheduled usage refreshes. The changed paths control background CLI eligibility and the error reported after a cached web session expires.

flowchart LR
A[Scheduled Claude refresh] --> B[Provider strategy selection]
B --> C[OAuth credential probe]
C --> D[CLI background eligibility]
B --> E[Cached browser session]
E --> F[Browser cookie recovery]
D --> G[Usage result]
F --> G
Loading

Decision needed

Question Recommendation
After the error-propagation repair and runtime proof, should explicitly opted-in background refreshes be allowed to invoke the opaque Claude CLI when CodexBar confirms its direct OAuth credentials are absent? Approve the narrow opt-in exception: Retain the .always prompt-policy requirement, identified-profile gate, and confirmed-absence check before allowing the background CLI fallback.

Why: This changes the background credential and Keychain-interaction boundary; source can verify the guard mechanics, but accepting that consent policy requires maintainer intent.

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: The PR reports focused tests and lint, but provides no redacted after-fix trace from a real Claude background CLI or browser-cookie recovery path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Propagate replacement-session fetch failures (P2) - Once extractSessionKeyInfo has returned a replacement cookie, a failure from fetchUsage describes that replacement session. This catch discards it whenever invalidatedCacheError is set, leaving the UI with the stale-cache diagnosis; limit the fallback to failed cookie discovery and cover a replacement-session request failure.
  • Resolve merge risk (P1) - Merging the current catch would hide a replacement browser session’s own authentication or network failure behind an older cached-session error.
  • Resolve merge risk (P1) - The PR has focused mock-based tests but no redacted after-fix runtime trace proving the real Claude background CLI or cookie-recovery path respects the no-UI boundary.
  • Complete next step (P2) - The mechanical blocker is clear, but the new background credential boundary also requires maintainer sign-off and contributor runtime proof before merge.

Findings

  • [P2] Propagate replacement-session fetch failures — Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift:1433-1436
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production versus test delta production +71/-14, tests +490/-134 The authentication fallback change has extensive focused coverage, but the error-selection boundary lacks one regression case.

Merge-risk options

Maintainer options:

  1. Preserve replacement-session failures (recommended)
    Limit the stale-error fallback to failed cookie discovery and add coverage for a replacement session whose usage request fails.
  2. Add redacted runtime proof
    Provide a real after-fix Claude background CLI or browser-cookie recovery trace that shows the observed result without exposing credentials or private endpoints.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Split cookie discovery from the replacement-session fetch, add a regression test for a discovered replacement cookie whose usage request fails, and preserve the replacement failure.

Technical review

Best possible solution:

Return the initial cached-session error only when replacement cookie discovery fails; once a replacement session is found, surface its fetch result while retaining the existing explicit background opt-in boundary.

Do we have a high-confidence way to reproduce the issue?

Yes. With the existing test seam, invalidate the cached cookie, return a replacement session from browser discovery, and fail its usage request; the changed catch returns the stale error instead of that replacement-session failure.

Is this the best way to solve the issue?

No. The catch must cover cookie discovery only; a fetch attempted with a discovered replacement session should propagate its own result or error.

Full review comments:

  • [P2] Propagate replacement-session fetch failures — Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift:1433-1436
    Once extractSessionKeyInfo has returned a replacement cookie, a failure from fetchUsage describes that replacement session. This catch discards it whenever invalidatedCacheError is set, leaving the UI with the stale-cache diagnosis; limit the fallback to failed cookie discovery and cover a replacement-session request failure.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c4ed34d0e44a.

Labels

Label justifications:

  • P1: The PR addresses Claude scheduled-refresh failures that disrupt usage status for signed-in users.
  • merge-risk: 🚨 auth-provider: It changes fallback selection among Claude OAuth, CLI, and browser-cookie credentials.
  • merge-risk: 🚨 availability: It changes background refresh eligibility and failure reporting.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR reports focused tests and lint, but provides no redacted after-fix trace from a real Claude background CLI or browser-cookie recovery path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

Likely related people:

  • Peter Steinberger: Recent descriptor history includes the Claude CLI fallback availability work and extensive maintenance of this provider path. (role: recent area contributor; confidence: high; commits: 4a83b87aff22, 330ae4384b18; files: Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift, Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift)
  • pickaxe: History credits this contributor with earlier Claude background marker scoping and revocation work adjacent to the changed eligibility policy. (role: background-gating contributor; confidence: medium; commits: 0250a73fc0a2, 5e5ca66ee986; files: Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Return the replacement-session fetch error and add a focused regression test.
  • Add a redacted real runtime trace to the PR body; updating it should trigger re-review, or a maintainer can request @clawsweeper re-review.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (7 earlier review cycles)
  • reviewed 2026-08-09T12:36:58.803Z sha f7ddb5e :: needs real behavior proof before merge. :: [P3] Update the background-fallback comment
  • reviewed 2026-08-09T12:43:11.155Z sha f7ddb5e :: needs real behavior proof before merge. :: [P3] Update the background-fallback comment
  • reviewed 2026-08-09T14:15:55.887Z sha f7ddb5e :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-09T15:57:43.150Z sha 401d9f2 :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-09T16:06:26.173Z sha 401d9f2 :: needs real behavior proof before merge. :: [P2] Propagate errors after a replacement session is found
  • reviewed 2026-08-09T18:15:49.810Z sha 401d9f2 :: needs real behavior proof before merge. :: [P2] Propagate replacement-session fetch errors
  • reviewed 2026-08-09T20:12:24.076Z sha 401d9f2 :: needs real behavior proof before merge. :: [P2] Preserve replacement-session fetch failures

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 9, 2026
ClaudeWebAPIFetcher.fetchUsageSerialized cleared a stale cached session
cookie on an auth failure and unconditionally fell through to a browser
cookie read to recover, exactly the pattern fixed for Ollama in PR steipete#2814.
In a background context BrowserCookieAccessGate typically denies that
read (no interactive Keychain prompt outside a user-initiated action),
so extractSessionKeyInfo finds no session key and the generic 'no
session key found' error replaces the original, more informative
cached-auth error (e.g. the OAuth-absence message) — surfacing as the
same intermittent flicker already reported for Claude.

Still attempt browser-cookie recovery after clearing the stale cache,
even in a background context — BrowserCookieAccessGate already gates
that read on its own no-UI preflight (Safari never needs Keychain
decryption, and a Chromium browser with a prior 'Always Allow' Keychain
grant is read without a prompt) — so a background attempt is not
unconditionally blocked. Only when that attempt also fails do we now
surface the original cached-auth error instead of the misleading
generic one.

Adds ClaudeWebBackgroundRecoveryTests covering: background recovery
that finds nothing (original error surfaces), background recovery that
succeeds without a prompt (still works), and the equivalent
user-initiated case.
@axisrow

axisrow commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 9, 2026
…ests

The background-Auto deadlock-breaker requires an identified Claude profile
(captureMarker -> identifiedSessionScope -> accountConfigURL). Without
CLAUDE_CONFIG_DIR in the test environment, accountConfigURL falls back to
the host's real ~/.claude.json, so these tests passed only on machines
with a signed-in Claude CLI and failed closed on CI where that file is
absent. Give each affected test its own identified profile via an
isolated CLAUDE_CONFIG_DIR so they prove the gate's behavior instead of
the host's sign-in state.
Merging main pushed ClaudeBaselineCharacterizationTests past the 800-line
type_body_length limit; move the explicit source-mode resolution and token
heuristic tests into a MARK-ed extension.
Same host-state leak as the baseline suite: without CLAUDE_CONFIG_DIR the
deadlock-breaker's marker guard reads the host's real ~/.claude.json, so
the test passed only on signed-in dev machines and failed on CI.
@steipete
steipete merged commit 89ee921 into steipete:main Aug 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"No available fetch strategy" for Claude provider

2 participants