Skip to content

Fix Claude background Keychain prompts#2191

Merged
steipete merged 9 commits into
steipete:mainfrom
Yuxin-Qiao:agent/fix-claude-keychain-loop
Jul 16, 2026
Merged

Fix Claude background Keychain prompts#2191
steipete merged 9 commits into
steipete:mainfrom
Yuxin-Qiao:agent/fix-claude-keychain-loop

Conversation

@Yuxin-Qiao

@Yuxin-Qiao Yuxin-Qiao commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • prevent background Auto refresh from launching either opaque Claude CLI Keychain path unless the user explicitly selected the Always allow Keychain prompts policy:
    • claude auth status --json preflight
    • delegated OAuth-expiry /status refresh
  • prevent planning an expired Claude CLI OAuth credential for background Auto refresh when that delegated refresh is disallowed
  • honor the global Disable Keychain Access setting before invoking the Claude CLI, including its child /usr/bin/security access
  • stop background MCP-only Claude credential discovery from touching Keychain under the default user-action-only policy
  • add stubbed regressions plus an opt-in live macOS proof for background and explicit-user behavior

Root cause

CodexBar had two background Claude CLI paths: the claude auth status --json preflight and the delegated OAuth-expiry /status refresh. Both can invoke /usr/bin/security outside CodexBar's in-process Keychain prompt policy. That allowed recurring Claude Code-credentials dialogs during background refreshes even when prompts were disabled. Separately, a Security.framework MCP-only discovery path could read the same item from background work despite the user-action-only policy.

This patch treats child-process Keychain behavior as opaque: background Auto refresh does not start it unless the user explicitly opts into background Keychain access. Explicit refresh and explicit CLI use remain available.

Fixes #2115.

Live affected-Mac proof (redacted)

Tested on a real macOS account with Claude Code 2.1.207 installed, a live OAuth login, and the default restrictive prompt policy (onlyOnUserAction). The opt-in proof exercises production code, not a process stub:

$ LIVE_CLAUDE_KEYCHAIN_PROOF=1 swift test --disable-sandbox \
    --scratch-path /private/tmp/codexbar-pr2191-proof \
    --filter ClaudeKeychainLiveProofTests

Suite ClaudeKeychainLiveProofTests started.
Test "live background Auto skips the opaque Claude Keychain boundary" started.
Claude OAuth delegated refresh skipped by Keychain prompt policy
Test "live background Auto skips the opaque Claude Keychain boundary" passed after 0.004 seconds.
Test "live explicit user auth probe reports Claude login" started.
Test "live explicit user auth probe reports Claude login" passed after 0.195 seconds.
Suite ClaudeKeychainLiveProofTests passed after 0.200 seconds.
Test run with 2 tests in 1 suite passed after 0.200 seconds.

The background assertion returns .skippedByPromptPolicy before either opaque Claude CLI//usr/bin/security boundary can launch. The explicit-user assertion invokes the real installed claude auth status --json path and correctly reports the existing OAuth login. No email, token, Keychain item, or account identifier is collected or printed. CI leaves this live suite disabled unless LIVE_CLAUDE_KEYCHAIN_PROOF=1 is explicitly set.

Validation

  • LIVE_CLAUDE_KEYCHAIN_PROOF=1 swift test --disable-sandbox --scratch-path /private/tmp/codexbar-pr2191-proof --filter ClaudeKeychainLiveProofTests — 2 real macOS tests passed
  • swift test --filter 'ClaudeBaselineCharacterizationTests|ClaudeOAuthCredentialsStoreMCPOnlyGuardTests|ClaudeCLIAuthStatusProbeTests|ClaudeOAuthDelegatedRefreshCoordinatorTests|ClaudeOAuthFetchStrategyAvailabilityTests' — 48 tests passed
  • changed-file SwiftFormat, SwiftLint, and git diff --check — clean
  • make check — repository checks and formatting complete; SwiftLint reported then I fixed one new line-length warning. The check's final result is otherwise blocked by its existing sandbox-generated plist write behavior.

@Yuxin-Qiao
Yuxin-Qiao marked this pull request as ready for review July 15, 2026 14:48
@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. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 16, 2026
@clawsweeper

clawsweeper Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed July 16, 2026, 10:20 AM ET / 14:20 UTC.

Summary
The branch blocks Claude background CLI auth preflight, delegated OAuth repair, expired-credential planning, and MCP-only Keychain discovery according to Keychain policy, with focused regressions and an opt-in live macOS suite.

Reproducibility: yes. from source inspection: the experimental reader can produce an effective .always mode while the stored policy remains .onlyOnUserAction, and the two new descriptor guards then permit background opaque CLI work.

Review metrics: 1 noteworthy metric.

  • Changed surface: 4 production files, 8 test files. The patch spans provider planning, delegated refresh, direct Keychain discovery, user-facing errors, and macOS and Linux regression coverage.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🦐 gold shrimp
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Replace both descriptor uses of the effective/current prompt mode with the stored mode and add an experimental-reader regression.
  • [P1] Add redacted affected-host output or a recording showing full background Auto avoids both Claude CLI paths and explicit CodexBar Refresh succeeds.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The redacted live output is useful but proves only a direct delegated coordinator skip and direct auth probe, not full background Auto remaining prompt-free plus explicit CodexBar Refresh recovery; add that affected-host proof, redact private data, update the PR body to trigger review, or ask a maintainer to comment @clawsweeper re-review.

Risk before merge

  • [P1] With securityCLIExperimental selected and the stored policy still .onlyOnUserAction, merging the current head can continue launching claude auth status --json in background Auto and can plan expired CLI OAuth credentials that the delegated coordinator later refuses to refresh.
  • [P1] The intended fail-closed policy removes background Claude CLI fallback for default-policy users, so affected setups may show no Claude data until an explicit Refresh or a stored .always opt-in; focused upgrade behavior must remain visible and tested.
  • [P1] The supplied live output covers a direct coordinator call and direct auth probe, not the full app Auto pipeline or explicit Refresh recovery requested by the prior review.

Maintainer options:

  1. Correct the stored-mode checks first (recommended)
    Change both descriptor gates to use the stored prompt mode, add the experimental-reader regression, and obtain full Auto/Refresh live proof before merge.
  2. Pause for the broader policy decision
    Hold this PR if maintainers want Proposal: unify credential access policy across providers #2214 to define a provider-neutral child-process policy before landing Claude-specific fail-closed behavior.

Next step before merge

  • [P1] Ask the contributor to fix the repeated stored-mode P1 and provide full affected-host Auto/Refresh proof; automation cannot supply the required real credential-environment evidence.

Security
Cleared: The diff narrows credential-capable background execution and introduces no dependency, workflow, permission, secret-handling, or supply-chain regression; the remaining policy bypass is covered as a functional auth-provider finding.

Review findings

  • [P1] Use the stored mode for both opaque CLI gates — Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift:336
Review details

Best possible solution:

Use the stored Claude prompt preference at every opaque child-process gate, preserve explicit-user and stored-.always behavior, then prove a real background Auto run stays prompt-free and an explicit CodexBar Refresh succeeds.

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

Yes from source inspection: the experimental reader can produce an effective .always mode while the stored policy remains .onlyOnUserAction, and the two new descriptor guards then permit background opaque CLI work.

Is this the best way to solve the issue?

No, not at the current head; the fail-closed boundary is maintainable, but both child-process checks must use the stored policy and the end-to-end recovery path still needs real proof.

Full review comments:

  • [P1] Use the stored mode for both opaque CLI gates — Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift:336
    The new guards call ClaudeOAuthKeychainPromptPreference.current(), which becomes the strategy-adjusted effective mode .always with securityCLIExperimental even when the user stored .onlyOnUserAction. That still permits the background auth preflight and expired-credential planning without the explicit opt-in promised by this patch; use the stored mode at both gates and cover that exact reader/preference combination. This second occurrence was already visible at the earlier reviewed head and should have been called out then.
    Confidence: 0.99
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: The PR addresses a recurring Claude credential prompt bug with meaningful but provider-specific impact and a bounded repair path.
  • merge-risk: 🚨 compatibility: The patch intentionally removes default-policy background CLI fallback, which can change data availability for existing Auto-mode setups.
  • merge-risk: 🚨 auth-provider: The diff changes Claude OAuth credential planning, CLI authentication probing, delegated refresh, and Keychain discovery behavior.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The redacted live output is useful but proves only a direct delegated coordinator skip and direct auth probe, not full background Auto remaining prompt-free plus explicit CodexBar Refresh recovery; add that affected-host proof, redact private data, update the PR body to trigger review, or ask a maintainer to comment @clawsweeper re-review.
Evidence reviewed

What I checked:

Likely related people:

  • ratulsarna: Introduced and merged the Claude prompt-policy UI and experimental Security CLI reader whose stored-versus-effective semantics are central to this review. (role: feature owner; confidence: high; commits: fc027a6, dd2d746, 78effa0; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials+SecurityCLIReader.swift, Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthDelegatedRefreshCoordinator.swift)
  • steipete: Authored and merged the current-main Claude background auth-status preflight and recently reviewed its fail-closed compatibility behavior. (role: recent area contributor and merger; confidence: high; commits: f63d43f021bc, e486c6c5f0ef; files: Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift, Tests/CodexBarTests/ClaudeBaselineCharacterizationTests.swift, Tests/CodexBarTests/ClaudeWebFetchDeadlineTests.swift)
  • afarwind: Reported and contributed the merged background Auto auth-preflight work that established the opaque Claude CLI path being modified. (role: introduced adjacent behavior; confidence: medium; commits: 88396220, f63d43f021bc; files: Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift, Tests/CodexBarTests/ClaudeBaselineCharacterizationTests.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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.

How this review workflow works
  • 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.
Review history (3 earlier review cycles)
  • reviewed 2026-07-16T03:19:45.211Z sha 1a6d6c6 :: needs real behavior proof before merge. :: [P1] Use the stored prompt mode for opaque CLI gating
  • reviewed 2026-07-16T09:47:30.566Z sha e9a6242 :: needs real behavior proof before merge. :: [P1] Use the stored prompt mode for opaque CLI gating
  • reviewed 2026-07-16T13:37:40.181Z sha 03b47f8 :: needs real behavior proof before merge. :: none

@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: 0fbdf6e89c

ℹ️ 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".

Comment on lines +659 to +660
guard !KeychainAccessGate.isDisabled,
ClaudeOAuthKeychainPromptPreference.current() == .always

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 Honor stored prompt mode before CLI auth preflight

In background Auto refresh with the experimental securityCLIExperimental reader selected, ClaudeOAuthKeychainPromptPreference.current() returns the effective mode .always even when the stored user preference is still the default .onlyOnUserAction. That lets this guard pass and then runs ClaudeCLIAuthStatusProbe.isLoggedIn (claude auth status --json) in the background, reintroducing the opaque Claude CLI Keychain path this change is trying to block unless the user explicitly opted into always allowing prompts. Use the stored prompt mode for this child-process boundary, as the delegated refresh path now does.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 16, 2026
@clawsweeper

clawsweeper Bot commented Jul 16, 2026

Copy link
Copy Markdown

ClawSweeper status: review started.

I am starting a fresh review of this pull request: Fix Claude background Keychain prompts This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

@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: 2ac078d6a9

ℹ️ 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".

Comment on lines +174 to +175
if configuration.interaction == .background,
configuration.keychainAccessDisabled || configuration.promptMode != .always

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 Honor disabled Keychain access for user refreshes

If KeychainAccessGate.isDisabled is true but the expired OAuth repair is triggered by a user action, this guard does not return because it is scoped to background interactions only. The coordinator then continues to touchOAuthAuthPath and launches claude /status, which can invoke /usr/bin/security, so the global “Disable Keychain access” setting is bypassed for explicit refresh/OAuth-source repairs.

Useful? React with 👍 / 👎.

return false
}
guard ClaudeOAuthKeychainPromptPreference.effectiveMode(readStrategy: readStrategy) != .never else {
let promptMode = ClaudeOAuthKeychainPromptPreference.effectiveMode(readStrategy: readStrategy)

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 Use stored prompt policy for security CLI MCP probes

When the experimental security CLI reader is selected, effectiveMode(readStrategy:) maps the stored .onlyOnUserAction or .never preference to .always, so this MCP-only check still launches /usr/bin/security during background expired-credential handling before delegated refresh is suppressed. This leaves a background Keychain prompt path open for users on securityCLIExperimental; use the stored prompt mode for this child-process boundary as well.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 5f136af into steipete:main Jul 16, 2026
8 checks passed
@steipete

Copy link
Copy Markdown
Owner

Maintainer verification after merge:

  • Reviewed the full external diff and adjacent Claude OAuth/CLI prompt-policy paths; no suspicious URLs, exfiltration, dependency changes, or unrelated scope found.
  • Added commit 2ac078d6a9ec19e15eb4b6dc43ff49d18cfd5c9f so both opaque Claude CLI background guards use the stored prompt preference, with regression coverage for the experimental-reader mismatch.
  • Ran CODEXBAR_SUPPRESS_TEST_KEYCHAIN_ACCESS=1 swift test --filter 'Claude(OAuthFetchStrategyAvailability|BaselineCharacterization|OAuthDelegatedRefreshCoordinator|OAuthCredentialsStoreMCPOnlyGuard)Tests': 47 tests in 4 suites passed.
  • Ran make check: SwiftFormat clean and SwiftLint reported 0 violations across 1,430 files; repository checks passed.
  • Ran autoreview on the maintainer patch and final commit: no accepted/actionable findings.
  • Exact-head CI for 2ac078d6a9ec19e15eb4b6dc43ff49d18cfd5c9f was fully green: changes, lint, Linux x64/arm64 builds, both macOS test shards, aggregate lint-build-test, and GitGuardian.
  • Merged with merge commit 5f136af0899f9867165bae5b23b899065b2793f0.

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: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. 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.

Constantly reoccuring keychain prompt for "Claude Code-credentials"

2 participants