Skip to content

Fix Claude never-prompt Keychain cache bypass (#1991)#2011

Open
Yuxin-Qiao wants to merge 10 commits into
steipete:mainfrom
Yuxin-Qiao:fix/claude-never-prompt-keychain-cache
Open

Fix Claude never-prompt Keychain cache bypass (#1991)#2011
Yuxin-Qiao wants to merge 10 commits into
steipete:mainfrom
Yuxin-Qiao:fix/claude-never-prompt-keychain-cache

Conversation

@Yuxin-Qiao

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

Copy link
Copy Markdown
Contributor

Summary

  • When Claude prompt mode is stored as never (Avoid Keychain prompts), CodexBar no longer reads/writes/clears the CodexBar-owned oauth.claude Keychain cache during OAuth load, invalidation, or cache presence checks.
  • Background refresh under never now prefers ~/.claude/.credentials.json (and existing Claude-keychain paths governed by the never policy) instead of repeatedly touching the CodexBar OAuth cache item that can surface macOS prompts.
  • CodexBar-owned memory cache entries are also bypassed under never, so a prior prompt-enabled load cannot keep serving stale oauth.claude cache data after the user switches modes.
  • Deferred invalidation uses a pending-clear tombstone: explicit logout / file-change invalidation under never records the pending marker and flushes it once prompt-enabled cache access is safe again (retrying on locked-keychain failures).
  • MCP-only detection stays available for the experimental security(1) reader (effectiveMode(readStrategy:)), while Security.framework probes remain blocked under stored never.
  • Owner-resolution probes of Claude Code-credentials are skipped when stored prompt mode is never.
  • Adds focused regression tests in ClaudeOAuthCredentialsStoreNeverPromptCacheTests plus updates to ownership / security-CLI suites.

Fixes the bug reported in #1991 where periodic refresh could still trigger macOS Keychain UI even after choosing Avoid Keychain prompts. #1990 (manual-refresh pre-alert UX) is intentionally unchanged.

Codex review follow-ups

Review item Resolution
Prevent stale cache reuse after never-mode invalidation clearCacheKeychain() records pendingCodexBarOAuthKeychainCacheClear under never; flush on re-enable (28f7c02b, tests)
Ignore cache-derived memory entries in never mode shouldUseCodexBarOAuthKeychainCache gates memory + cache reads (3c340b40, tests)
Update stale never-mode cache ownership test load record ignores codexbar cache in never prompt mode; prior assertion moved to .onlyOnUserAction (e5ca4498)
Avoid reporting the same file change forever shouldSaveFileFingerprint under never + idempotent second invalidateCacheIfCredentialsFileChanged() (3c340b40, tests)
Keep pending clear until keychain delete succeeds flushPendingCodexBarOAuthKeychainCacheClearIfNeeded() retains tombstone on .failed; load returns .temporarilyUnavailable (e5ca4498, tests)
Preserve MCP-only detection for experimental reader effectiveMode(readStrategy:) in isMcpOAuthOnlyClaudeKeychainPayloadPresent (e5ca4498, tests)
Keep pending clear until replacement store succeeds saveToCacheKeychain clears tombstone only after successful storeResult (28f7c02b, tests)

Behavior proof (redacted, no live Keychain UI)

Repo policy (AGENTS.md) avoids live macOS Keychain prompt validation in CI/agents. Proof below is from isolated test-keychain runs on macOS 15 using per-test KeychainCacheStore.withServiceOverrideForTesting (no user Keychain prompts).

$ swift test --filter 'ClaudeOAuthCredentialsStoreNeverPromptCacheTests|ClaudeOAuthCredentialsStoreCLIStorageOwnershipTests|ClaudeOAuthCredentialsStoreIsolatedSecurityCLITests'
...
✔ Test "never prompt mode load prefers credentials file over codexbar oauth keychain cache in background" passed
✔ Test "never prompt mode load does not write codexbar oauth keychain cache" passed
✔ Test "never prompt mode invalidate credentials file changed leaves codexbar oauth keychain cache untouched" passed
✔ Test "never prompt mode has cached credentials ignores codexbar oauth keychain cache" passed
✔ Test "leaving never prompt mode clears stale codexbar oauth keychain cache after credentials file change" passed
✔ Test "never prompt mode ignores memory cache populated under prompt mode" passed
✔ Test "never prompt mode invalidate cache records pending clear for explicit logout" passed
✔ Test "pending oauth keychain cache clear retries when keychain delete temporarily unavailable" passed
✔ Test "save to cache keychain keeps pending clear when replacement store fails" passed
✔ Test "never prompt mode still detects MCP-only payload via experimental security CLI reader" passed
✔ Test "load record ignores codexbar cache in never prompt mode" passed
✔ Test run with 21 tests in 3 suites passed

Representative redacted logs from the never-prompt suite:

info com.steipete.codexbar.claude-usage: Claude OAuth credentials file changed; invalidating cache
info com.steipete.codexbar.keychain-cache: Keychain cache delete temporarily unavailable (oauth.claude)

The second line is from the simulated errSecInteractionNotAllowed retry path; under never the production oauth.claude item is not consulted until the pending clear succeeds.

Live macOS repro (installed app 0.41.0)

On a real setup against the installed 0.41.0 (100) app (not just isolated tests):

  1. Seed oauth.claude under com.steipete.codexbar.cache
  2. Set claudeOAuthKeychainPromptMode = never
  3. Run CodexBarCLI usage --provider claude --source oauth

Observed: CLI hung for >=12s waiting for macOS Keychain UI while never mode was active.

Helper script (added in 4a41e582):

./Scripts/repro_keychain_issues.sh 2025

Details: #2011 (comment)

Test plan

  • swift test --filter 'ClaudeOAuthCredentialsStoreNeverPromptCacheTests|ClaudeOAuthCredentialsStoreTests|ClaudeOAuthCredentialsStorePromptPolicyTests|ClaudeOAuthCredentialsStoreCLIStorageOwnershipTests|ClaudeOAuthCredentialsStoreIsolatedSecurityCLITests'
  • make check (lint green on latest CI rollup)
  • Redacted macOS behavior proof via isolated test-keychain + structured logs (above)
  • Live macOS repro on installed 0.41.0 app + Scripts/repro_keychain_issues.sh

Maintainer-requested live proof is in the PR comment above; isolated tests remain the default CI path per AGENTS.md.

When users choose Avoid Keychain prompts, skip CodexBar-owned oauth.claude cache and Claude keychain presence probes that could still trigger macOS Keychain UI during background refresh.

Co-authored-by: Cursor <cursoragent@cursor.com>
@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed July 9, 2026, 2:11 PM ET / 18:11 UTC.

Summary
The PR gates Claude OAuth CodexBar-owned Keychain cache reads, writes, clears, and memory-cache reuse when prompt mode is .never, adds pending-clear handling, preserves experimental security-CLI MCP detection, and adds focused Swift regression tests.

Reproducibility: yes. source-backed: the linked issue includes affected-machine diagnostics, and current main reads the CodexBar-owned Claude OAuth cache before file fallback even when prompt mode is .never. I did not run live Keychain validation because AGENTS.md forbids unrequested prompt-producing checks.

Review metrics: 2 noteworthy metrics.

  • Changed surface: 2 source files, 3 test files. The implementation is concentrated in Claude OAuth cache and security-CLI behavior with matching provider-focused tests.
  • Regression suite added: 586-line test file added. Most of the diff is focused coverage for never-mode cache reads, writes, invalidation, memory bypass, and pending-clear failure paths.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #1991
Summary: This PR is the candidate fix for the canonical v0.41.0 periodic Claude Keychain prompt loop; the manual-refresh prompt report overlaps but is intentionally outside this PR.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦞 diamond lobster
Result: blocked until real behavior proof from a real setup is added.

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

Rank-up moves:

  • [P1] Add redacted affected-app runtime proof, preferably unified logs across a refresh window showing no CodexBar oauth.claude Keychain activity while .never is set.
  • Redact private data in any proof, including tokens, account identifiers, machine details, endpoints, and non-public paths.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body includes copied focused Swift test output and simulated logs from isolated test-store/test-keychain runs, but no after-fix runtime proof from an affected CodexBar app setup. 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.

Risk before merge

  • [P1] The only after-fix proof in the PR body is copied Swift test output and simulated logs from isolated test-store/test-keychain runs, not redacted runtime evidence from an affected CodexBar app setup.
  • [P1] The diff intentionally changes Claude OAuth cache behavior for existing .never users; maintainers should confirm the upgrade behavior for cache-only setups with no current credentials file is acceptable before merge.

Maintainer options:

  1. Require affected-app proof (recommended)
    Before merge, require redacted runtime proof from an affected setup or maintainer machine showing .never no longer touches the CodexBar Claude OAuth cache during refresh.
  2. Accept isolated coverage intentionally
    Maintainers may choose to accept the isolated test-store proof because repository policy avoids live Keychain prompt validation.
  3. Hold for auth-policy review
    Pause merge if the cache-only upgrade behavior under .never needs a product or auth-policy decision first.

Next step before merge

  • [P1] No automated repair lane is appropriate because the remaining blocker is contributor or maintainer proof sufficiency for an auth-sensitive Keychain cache change.

Maintainer decision needed

  • Question: For this auth-sensitive Claude Keychain cache fix, should maintainers require affected-app runtime proof before merge, or accept the isolated test-store proof under the repo's no-live-Keychain-prompt policy?
  • Rationale: The patch changes OAuth credential cache behavior and repository policy discourages live prompt-producing validation, so the remaining blocker is proof sufficiency rather than a line-level code defect.
  • Likely owner: steipete — steipete is the strongest current release and Keychain prompt policy owner in the relevant history.
  • Options:
    • Require affected-app proof (recommended): Ask for redacted after-fix runtime evidence, such as unified logs across a refresh window showing no CodexBar Keychain activity for oauth.claude while .never is set.
    • Accept isolated proof: A maintainer can explicitly override the proof gate if isolated test-keychain coverage is considered sufficient for this prompt path.
    • Pause for maintainer machine validation: Keep the PR open until someone with a safe affected setup can validate the no-prompt behavior without exposing private credentials.

Security
Cleared: No concrete security or supply-chain regression was found; the remaining auth-sensitive concern is proof and upgrade behavior rather than a discrete security defect.

Review details

Best possible solution:

Land the focused cache-bypass fix after redacted affected-app runtime proof or an explicit maintainer proof override confirms that .never avoids oauth.claude Keychain touches without regressing Claude auth recovery.

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

Yes, source-backed: the linked issue includes affected-machine diagnostics, and current main reads the CodexBar-owned Claude OAuth cache before file fallback even when prompt mode is .never. I did not run live Keychain validation because AGENTS.md forbids unrequested prompt-producing checks.

Is this the best way to solve the issue?

Yes for the code shape: gating the Claude-specific CodexBar cache above KeychainCacheStore is narrower than changing the global cache layer, and the PR covers stale-cache tombstone paths. The merge still needs real behavior proof or a maintainer proof override for the auth-sensitive runtime path.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a normal-priority Claude auth-provider prompt-loop fix with limited blast radius and no confirmed data loss or core runtime outage.
  • merge-risk: 🚨 auth-provider: The PR changes Claude OAuth credential cache read/write/clear behavior and could affect token loading or recovery for existing .never prompt-mode users.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦞 diamond lobster.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body includes copied focused Swift test output and simulated logs from isolated test-store/test-keychain runs, but no after-fix runtime proof from an affected CodexBar app setup. 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

What I checked:

Likely related people:

  • steipete: Current blame on the shipped Claude OAuth cache path collapses to the v0.41.0 release commit, and recent history shows Keychain prompt trust and cache-store work in this area. (role: recent area contributor and release owner; confidence: high; commits: a83a83fa4131, 8b417a066cb1, 24fe798f3769; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift, Sources/CodexBarCore/KeychainCacheStore.swift, docs/keychain-prompts.md)
  • ratulsarna: GitHub path history shows the densest earlier work on Claude OAuth prompt policy, delegated recovery, security-CLI reading, and keychain-cache lock handling. (role: feature-history owner; confidence: high; commits: 0e7da356a37f, 5b52899ea62a, 8736b0a4460c; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift, Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials+SecurityCLIReader.swift, Sources/CodexBarCore/KeychainCacheStore.swift)
  • ss251: Recent main history ties ss251 to Claude OAuth cache/history correctness around account switching and stale credential ownership. (role: recent adjacent contributor; confidence: medium; commits: 9a0028442e35; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift)
  • Yuxin-Qiao: Although also the PR author, prior merged history ties Yuxin-Qiao to background Claude delegated-refresh and MCP-only keychain-touch suppression adjacent to this prompt loop. (role: recent adjacent contributor; confidence: medium; commits: a27c9b094e61; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift, Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials+SecurityCLIReader.swift, Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.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 (4 earlier review cycles)
  • reviewed 2026-07-09T16:42:57.091Z sha 8a42ab7 :: needs real behavior proof before merge. :: [P1] Keep skipped cache invalidation pending
  • reviewed 2026-07-09T16:56:43.438Z sha f37327e :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-09T17:47:21.358Z sha 28f7c02 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-09T18:05:23.456Z sha ab83015 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added 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. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 9, 2026

@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: 8a42ab76ef

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

}

private static func clearCacheKeychain() {
guard self.shouldUseCodexBarOAuthKeychainCache else { return }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent stale Claude cache reuse after never-mode invalidation

When prompt mode is .never, invalidateCache() still clears memory but this guard makes the persistent oauth.claude cache survive explicit login/logout invalidation. If the user later switches back to onlyOnUserAction/always, loadRecord consults the CodexBar keychain cache before the credentials file, so a pre-logout/pre-reauth token can be resurrected. Avoid consuming the invalidation without either clearing the item when safe or recording a tombstone/pending clear so the cache cannot be used after prompt mode changes.

Useful? React with 👍 / 👎.

Do not save the file fingerprint while CodexBar OAuth Keychain cache work is skipped, so switching back to a prompt-enabled mode can clear stale oauth.claude entries before load.

Co-authored-by: Cursor <cursoragent@cursor.com>
@clawsweeper clawsweeper Bot removed 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. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 9, 2026

@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: f37327eabd

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

var cacheTemporarilyUnavailable = false

switch KeychainCacheStore.load(key: ClaudeOAuthCredentialsStore.cacheKey, as: CacheEntry.self) {
switch ClaudeOAuthCredentialsStore.loadCodexBarOAuthKeychainCache() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore cache-derived memory entries in never mode

The cache bypass added here is only applied once execution reaches KeychainCacheStore; loadRecord returns the 30-minute memory cache before this helper, and a prior non-never load from the CodexBar OAuth keychain cache populates that memory cache. If the user switches to .never while the app is running, subsequent loads can still return that cache-derived credential before checking the credentials file, so the never-mode bypass can serve stale keychain-cache data until the memory TTL expires or file invalidation happens. Consider clearing/ignoring memory cache when this predicate is false, or preserving provenance and rejecting cache-derived memory records.

Useful? React with 👍 / 👎.

}

private static var shouldUseCodexBarOAuthKeychainCache: Bool {
ClaudeOAuthKeychainPromptPreference.storedMode() != .never

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the stale never-mode cache ownership test

With this predicate returning false for .never, loadRecord no longer sees CodexBar OAuth keychain entries in that mode, but Tests/CodexBarTests/ClaudeOAuthCredentialsStoreCLIStorageOwnershipTests.swift still asserts a .never load returns a .cacheKeychain record at lines 481-496. In that test setup there is no credentials file and repair is disabled, so the call now throws .notFound and leaves the suite red; update the old expectation (for example to run under a non-never mode) alongside this behavior change.

Useful? React with 👍 / 👎.

Comment on lines +499 to +500
} else {
shouldClearKeychainCache = true
shouldSaveFileFingerprint = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid reporting the same file change forever

When prompt mode is .never, this path deliberately skips saving the new credentials-file fingerprint, but invalidateCacheIfCredentialsFileChanged() still returns true. UsageStore.captureClaudeRefreshAuthState treats that return value as a fresh credential change and clears Claude credential-derived state on each refresh (see UsageStore+Refresh.swift lines 445-447 and 225-226), so a user who starts in never mode or changes the credentials file while in never mode will repeatedly reset Claude state until they leave the mode. Preserve the pending keychain-cache invalidation separately from the public “changed” return value, or save enough fingerprint state to make subsequent calls return false while the file is unchanged.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added 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. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 9, 2026
Defer explicit invalidateCache keychain deletes while Keychain prompts are disabled, then flush the stale oauth.claude entry before the next prompt-enabled load.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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: 38a399deac

ℹ️ 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 2182 to 2183
self.clearPendingCodexBarOAuthKeychainCacheClear()
KeychainCacheStore.clear(key: self.cacheKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep pending clear until keychain delete succeeds

When a pending clear is flushed after leaving .never, this removes the tombstone before verifying that KeychainCacheStore.clear actually deleted the old cache item. If the delete is temporarily unavailable (for example errSecInteractionNotAllowed while the keychain is locked), the flag is gone but the stale pre-logout/pre-file-change OAuth cache remains, so a later unlocked read can resurrect it. Keep the pending marker until clearResult reports either removed or missing, and avoid reading the cache after a failed clear.

Useful? React with 👍 / 👎.

guard !keychainAccessDisabled || self.isolatedSecurityCLIKeychainPath(environment: environment) != nil else {
return false
}
guard ClaudeOAuthKeychainPromptPreference.storedMode() != .never else { return false }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve MCP-only detection for experimental reader

When the user has selected the experimental security(1) reader while their stored prompt preference is .never, ClaudeOAuthKeychainPromptPreference.current() intentionally treats that reader as .always and readRawClaudeKeychainPayloadViaSecurityCLIIfEnabled is non-interactive. This new stored-mode guard skips the MCP-only probe anyway, so background delegated refresh no longer takes the existing MCP-only short-circuit and instead runs the Claude CLI retry path for an MCP-only keychain. Gate only the Security.framework path here, or use the effective reader mode instead of the stored prompt preference.

Useful? React with 👍 / 👎.

Skip keychain-derived memory cache under never prompt, save credentials-file
fingerprints while deferring keychain clears, and align ownership tests.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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: 3c340b40d3

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

historyOwnerIdentifier: String? = nil)
{
guard self.shouldUseCodexBarOAuthKeychainCache else { return }
self.clearPendingCodexBarOAuthKeychainCacheClear()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep pending clear until replacement store succeeds

When a clear was deferred while prompt mode was .never, a later direct sync path such as syncFromClaudeKeychainWithoutPrompt can call this helper before any cache load flushes the pending clear. Removing the marker before KeychainCacheStore.store means a temporary store failure, for example while the CodexBar cache keychain is locked, leaves the stale pre-logout/pre-file-change cache item in place with no tombstone, so a subsequent load can reuse it. Clear the marker only after a successful replacement store, or delete/flush the stale item first and keep the marker on failure.

Useful? React with 👍 / 👎.

Retry tombstone flushes until keychain delete succeeds, fall back to the
credentials file while pending, and keep MCP-only detection for the
experimental security CLI reader under stored never prompt mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
@clawsweeper clawsweeper Bot removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jul 9, 2026
Flush deferred clears before writing, clear the tombstone only after
storeResult succeeds, and re-mark pending when replacement fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@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. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 9, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Addressed all seven @chatgpt-codex-connector P2 inline comments in commits 3c340b4028f7c02b (pending-clear tombstone, memory-cache bypass, fingerprint idempotency, MCP-only effectiveMode, ownership-test update, locked-keychain retry, store-failure handling).

Updated the PR body with redacted macOS behavior proof from isolated test-keychain runs (no live Keychain UI per AGENTS.md).

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 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: the existing ClawSweeper review comment will be edited in place when the review finishes.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Live macOS behavior proof (#1991 / #2025)

Reproduced on a local machine against the installed Homebrew/Sparkle app 0.41.0 (100), not just isolated Swift tests.

Setup

  • defaults write com.steipete.codexbar claudeOAuthKeychainPromptMode never
  • Seeded CodexBar-owned Keychain cache item service=com.steipete.codexbar.cache, account=oauth.claude
  • Ensured ~/.claude/.credentials.json fingerprint was already established (so refresh does not immediately clear the cache on first invalidation)
  • Stopped the menu-bar app to avoid concurrent Keychain access, then invoked:
/Applications/CodexBar.app/Contents/Helpers/CodexBarCLI usage --provider claude --source oauth

Result

  • CodexBarCLI hung for >=12s until killed, consistent with a visible macOS Keychain password prompt while never mode is active.
  • This matches the reported periodic refresh loop: background OAuth refresh still touches the CodexBar-owned oauth.claude cache even when direct Claude Code Keychain reads are blocked.

Repro helper

Added a small helper script on my branch:

./Scripts/repro_keychain_issues.sh 2025

It seeds the cache, switches to never, and watches for the expected hang.

Notes

Co-authored-by: Cursor <cursoragent@cursor.com>
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — added live macOS repro proof in the previous comment and pushed Scripts/repro_keychain_issues.sh in 4a41e582.

@clawsweeper

clawsweeper Bot commented Jul 10, 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: the existing ClawSweeper review comment will be edited in place when the review finishes.

@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: 4a41e5823d

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

}

switch KeychainCacheStore.load(key: ClaudeOAuthCredentialsStore.cacheKey, as: CacheEntry.self) {
switch ClaudeOAuthCredentialsStore.loadCodexBarOAuthKeychainCache() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't count pending-cleared OAuth cache as present

When this wrapper returns .temporarilyUnavailable because flushPendingCodexBarOAuthKeychainCacheClearIfNeeded() failed, the cache is known stale from an explicit never-mode invalidation rather than merely unreadable, but the existing branch below still makes hasCachedCredentials return true. ClaudeUsageFetcher.OAuthExecutor.load uses that boolean to disable OAuth keychain bootstrap prompts, so after a user invalidates/logout in .never, switches back, and the first cache delete is temporarily unavailable, the app can treat Claude as cached even though loadRecord will not use the stale item. Consider distinguishing pending-clear failure from an ordinary keychain read outage here and returning false for the pending-clear case.

Useful? React with 👍 / 👎.

Comment thread Scripts/repro_keychain_issues.sh Outdated
Comment on lines +106 to +108
else
rm -f "${HOME}/.claude/.credentials.json"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve credentials when running the 2024 repro

When this helper is run as Scripts/repro_keychain_issues.sh 2024, repro_2025 never creates .credentials.json.repro-backup, but the EXIT trap still reaches this else branch and removes the user's real ~/.claude/.credentials.json. The cleanup should only remove the synthetic file when this invocation created it, or snapshot/restore the original credentials file for every mode.

Useful? React with 👍 / 👎.

Comment thread Scripts/repro_keychain_issues.sh Outdated
else
rm -f "${HOME}/.claude/.credentials.json"
fi
defaults delete com.steipete.codexbar claudeOAuthKeychainPromptMode 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the user's prompt-mode preference

This cleanup unconditionally deletes the real claudeOAuthKeychainPromptMode default instead of restoring the value that was present before the repro. In 2024 mode the script never changes that setting, and in 2025/all it overwrites it with onlyOnUserAction/never, so running the helper can leave CodexBar with a different keychain prompt policy than the user configured.

Useful? React with 👍 / 👎.

Comment thread Scripts/repro_keychain_issues.sh Outdated
rm -f "${HOME}/.claude/.credentials.json"
fi
defaults delete com.steipete.codexbar claudeOAuthKeychainPromptMode 2>/dev/null || true
security delete-generic-password -s "com.steipete.codexbar.cache" -a "oauth.claude" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the existing Claude OAuth cache

This cleanup always deletes the real CodexBar oauth.claude keychain item; in 2024 mode the script never seeds that item, so it removes whatever cache the user already had, and in 2025/all the seeded entry overwrites the old one without any backup. For users whose usable Claude OAuth state is only in CodexBar's cache, running the repro helper can force re-authentication; use an isolated service/keychain or save and restore the previous item.

Useful? React with 👍 / 👎.

Treat stale oauth.claude cache as absent when a never-mode pending clear
flush fails, and make the live repro helper restore credentials, prompt
mode, and keychain cache instead of deleting user state.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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: 3cb19cc030

ℹ️ 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 thread Scripts/repro_keychain_issues.sh Outdated
Comment on lines +52 to +56
security add-generic-password \
-s "com.steipete.codexbar.cache" \
-a "oauth.claude" \
-l "CodexBar Cache" \
-w "$(cat "$CACHE_BACKUP")" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the cache item's trusted-app ACL

Fresh evidence in this revision is that the cache is now backed up and re-added, but the restore path only writes the saved secret; security add-generic-password documents -T appPath as the way to specify apps that may access the item, and by default the creating app is trusted. When a user already had an oauth.claude cache and runs this repro, cleanup deletes it and recreates it without the CodexBar/CodexBarCLI trusted-app entries used by the seeding path, so subsequent app reads can prompt or fail even though the script claims to restore the cache.

Useful? React with 👍 / 👎.

Comment thread Scripts/repro_keychain_issues.sh Outdated
Comment on lines +215 to +216
repro_2025 || true
repro_2024 || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate repro misses from default all mode

When the helper is run with its default all mode, these || true handlers discard each repro's failure status, so the script exits 0 after cleanup even if both commands finish early and wait_for_hang returns a miss. That makes automation or manual checks report a successful reproduction run when neither expected Keychain hang was observed; keep continuing to the second repro, but accumulate the statuses and return nonzero when the selected repros miss.

Useful? React with 👍 / 👎.

Comment on lines +33 to +35
set_prompt_mode() {
defaults write com.steipete.codexbar claudeOAuthKeychainPromptMode "$1"
PROMPT_MODE_CHANGED=1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply prompt mode to the CLI process

When reproducing #2025, this writes the app preferences domain, but the launched process is CodexBarCLI; the Claude prompt preference is resolved through ClaudeOAuthKeychainPromptPreference.storedMode() from UserDefaults.standard, and CLI startup does not switch standard defaults to com.steipete.codexbar (CLIHelpers explicitly opens that suite only for unrelated display options). On a normal packaged helper the CLI therefore keeps its default .onlyOnUserAction, so the never-mode cache bypass path being tested is not actually exercised unless the CLI domain happens to have the same value.

Useful? React with 👍 / 👎.

Comment thread Scripts/repro_keychain_issues.sh Outdated

repro_2024() {
log "=== #2024: browser Safe Storage via Claude web cookie import ==="
defaults delete com.steipete.codexbar browserCookieAccessDeniedUntil 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the browser-cookie cooldown after repro

When 2024 or the default all mode is selected, this deletes the real browserCookieAccessDeniedUntil default to force a Safe Storage attempt, but cleanup() never snapshots or restores it. If a user had recently denied browser cookie Keychain access, running the helper clears the six-hour suppression used by BrowserCookieAccessGate and the app can resume prompting immediately after the repro.

Useful? React with 👍 / 👎.

Write prompt mode to both app and CLI defaults domains, restore oauth cache
ACLs and browser-cookie cooldown state, and propagate repro misses in all mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
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. P2 Normal priority bug or improvement with limited blast radius. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant