Skip to content

Fix Claude no-prompt Keychain repair#2102

Merged
steipete merged 1 commit into
steipete:mainfrom
OfficialAbhinavSingh:fix-claude-keychain-never-noui-fallback
Jul 16, 2026
Merged

Fix Claude no-prompt Keychain repair#2102
steipete merged 1 commit into
steipete:mainfrom
OfficialAbhinavSingh:fix-claude-keychain-never-noui-fallback

Conversation

@OfficialAbhinavSingh

@OfficialAbhinavSingh OfficialAbhinavSingh commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #1975.

Summary

When Claude's Avoid Keychain prompts preference was set to Never, CodexBar blocked every Claude Keychain read. That also blocked guaranteed no-UI Security.framework reads, so a missing ~/.claude/.credentials.json could not be repaired from an otherwise valid Claude Code Keychain item.

This change makes the policy boundary explicit:

  • Never still blocks interactive Security.framework reads.
  • Never also blocks the experimental /usr/bin/security reader because that subprocess can prompt.
  • Reads protected by KeychainNoUIQuery remain available and fail closed instead of showing UI.
  • Advanced → Disable Keychain access remains the hard global kill switch and blocks every Keychain path.

The patch also updates the existing experimental-reader expectations and removes the proposed live verification script. Routine proof now uses isolated fixture stores only; it never changes real defaults or touches the login Keychain.

Verification

  • swift test --filter 'ClaudeOAuthCredentialsStoreTests|ClaudeOAuthCredentialsStoreSecurityCLITests|ClaudeOAuthCredentialsStoreNeverPromptCacheTests|ClaudeOAuthCredentialsStoreIsolatedSecurityCLITests' — 59 tests passed.
  • Source-blind behavior validation — 5/5 contracts passed: no-UI repair, zero security CLI calls, interactive denial, absent-item fail-closed, global disable.
  • make check — SwiftFormat and SwiftLint clean; repository/docs/package gates passed.
  • CODEXBAR_TEST_RETRY_NON_TIMEOUT_FAILURES=0 make test — 652/652 selections, 55/55 groups passed first attempt, zero retries and zero timeouts.
  • Maintainer autoreview — no actionable findings.

No live Keychain, defaults, browser, network, or account credentials were used during validation.

Thanks @OfficialAbhinavSingh for the report and original fix.

@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: 1af050af15

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

}

@Test
func `never mode repairs a missing credentials file from a valid no-UI Keychain read`() throws {

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 Guard macOS-only Keychain repair tests

This new test runs on every platform, but the repair path it asserts is inside #if os(macOS) and returns nil on non-macOS. In Linux SwiftPM test runs, loadRecord(... allowClaudeKeychainRepairWithoutPrompt: true) still throws .notFound here even with the synthetic Keychain override, so this suite will break cross-platform CI unless the suite/test is wrapped in #if os(macOS) or the test uses a platform-neutral seam.

Useful? React with 👍 / 👎.

@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. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Jul 12, 2026
@clawsweeper

clawsweeper Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed July 13, 2026, 5:35 AM ET / 09:35 UTC.

Summary
The PR permits guaranteed no-UI Claude Keychain reads in never-prompt mode, adds three macOS regression tests, and adds an isolated live-verification script.

Reproducibility: yes. at source level: current main blocks the documented missing-file repair at the never-mode gate, and the focused fixture reportedly reproduces the same .notFound result. Complete real signed-runtime before-and-after recovery has not been established.

Review metrics: 3 noteworthy metrics.

  • Patch surface: 3 files affected. The PR changes one credential-policy implementation file plus focused tests and a verification helper.
  • Diff size: 388 added, 17 removed. Most additions are regression and live-verification scaffolding around a comparatively small policy change.
  • Regression coverage: 3 tests added. The new suite covers missing-file repair, interactive-read denial, and the global Keychain kill switch under never mode.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #1975
Summary: This PR is the direct candidate fix for the linked issue; the merged cache-policy PR is related context but addressed a distinct Keychain item and failure mode.

Members:

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

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🐚 platinum hermit
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:

  • Post redacted signed-app or equivalent real-login output showing successful credential recovery with no prompt.
  • Obtain explicit maintainer confirmation of the .never versus global Disable Keychain access contract.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The real-macOS evidence shows the no-UI read starts under a real never preference, but it does not show successful after-fix recovery through the signed app; post a redacted Terminal transcript or recording, update the PR body, and rely on automatic re-review or ask a maintainer to comment @clawsweeper re-review.

Risk before merge

  • [P1] Existing users may interpret .never as prohibiting Claude Code Keychain access, while this PR redefines it as prohibiting interactive Keychain UI; that privacy-contract change is intentional but not yet maintainer-approved.
  • [P1] The posted runtime evidence proves that the lookup starts, not that the signed application successfully resolves and uses the credential without displaying UI on the affected setup.

Maintainer options:

  1. Approve and prove no-UI recovery (recommended)
    Confirm the preference contract and require a redacted signed-app run showing successful credential resolution without a prompt before merge.
  2. Keep strict zero access
    Reject the semantic change and move recovery behind a separate explicit action or setting.

Next step before merge

  • [P1] A maintainer should decide the preference/security contract and wait for successful signed-runtime recovery proof; there is no remaining narrow mechanical defect for an automated repair lane.

Maintainer decision needed

  • Question: Should Claude's Avoid Keychain prompts setting permit Security.framework reads configured to fail instead of displaying authentication UI, while the separate global Disable Keychain access setting remains the zero-access control?
  • Rationale: The implementation can enforce no UI, but it intentionally changes the meaning of an existing privacy preference, which requires maintainer intent rather than code inference.
  • Likely owner: steipete — As repository owner, steipete is the best available owner for the persisted preference contract and release-facing privacy semantics.
  • Options:
    • Allow fail-closed reads (recommended): Define .never as no interactive prompts and permit only verified no-UI Security.framework reads for credential repair.
    • Preserve zero access: Keep .never blocking all Claude Code Keychain reads and place recovery behind a separate explicit user action or setting.

Security
Needs attention: No concrete exploit is apparent, but the broadened never-mode Keychain boundary needs explicit maintainer approval and successful signed-runtime proof.

Review details

Best possible solution:

Define .never as a no-interactive-UI contract, retain Advanced → Disable Keychain access as the zero-access control, and require a redacted signed-app recovery transcript before merge.

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

Yes at source level: current main blocks the documented missing-file repair at the never-mode gate, and the focused fixture reportedly reproduces the same .notFound result. Complete real signed-runtime before-and-after recovery has not been established.

Is this the best way to solve the issue?

Yes technically, provided the caller audit is complete: distinguishing fail-closed no-UI reads is narrower than re-enabling prompts and preserves the global zero-access switch. The preference semantics still require maintainer approval.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.

Label justifications:

  • P2: This is a provider-specific credential-recovery bug with bounded but real user impact.
  • merge-risk: 🚨 auth-provider: The patch changes Claude credential-source eligibility under an existing persisted preference.
  • merge-risk: 🚨 security-boundary: The patch broadens Keychain access under never mode and therefore requires explicit approval of the no-UI security boundary.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The real-macOS evidence shows the no-UI read starts under a real never preference, but it does not show successful after-fix recovery through the signed app; post a redacted Terminal transcript or recording, update the PR body, and rely on automatic re-review or ask a maintainer to comment @clawsweeper re-review.
Evidence reviewed

Security concerns:

  • [medium] Approve the broadened never-mode boundary
    Existing users selected a mode that currently blocks this Keychain source; even fail-closed reads should not ship under that preference without an explicit decision that it promises no prompts rather than no access.
    Confidence: 0.96

What I checked:

Likely related people:

  • Yuxin-Qiao: Authored the merged never-prompt cache and prompt-policy work that defines the boundary this PR extends. (role: recent adjacent feature contributor; confidence: high; commits: 2d58d098335a; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift)
  • steipete: The repository owner is the strongest available routing candidate for the user-facing privacy semantics of the persisted Keychain preference. (role: repository owner and release decision owner; confidence: medium; commits: 3e05988f5c5f; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.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 (7 earlier review cycles)
  • reviewed 2026-07-12T20:58:12.703Z sha 1af050a :: needs real behavior proof before merge. :: [P1] Block the experimental CLI reader for no-prompt callers
  • reviewed 2026-07-12T21:24:29.894Z sha b822b4a :: needs real behavior proof before merge. :: [P1] Block the experimental CLI reader for no-prompt callers
  • reviewed 2026-07-12T21:45:30.776Z sha b822b4a :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-12T21:52:01.525Z sha b822b4a :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-12T22:11:37.398Z sha db7bf21 :: needs real behavior proof before merge. :: [P2] Preserve Keychain paths when restoring the search list
  • reviewed 2026-07-13T08:04:16.880Z sha a2f64b9 :: needs real behavior proof before merge. :: [P2] Restore Keychain search paths without shell splitting
  • reviewed 2026-07-13T09:03:57.846Z sha 0955012 :: needs real behavior proof before merge. :: none

@OfficialAbhinavSingh
OfficialAbhinavSingh force-pushed the fix-claude-keychain-never-noui-fallback branch from 1af050a to b822b4a Compare July 12, 2026 21:20
@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. I dug into the P1 ("Block the experimental CLI reader for no-prompt callers") and I don't think it holds as stated — this PR doesn't newly route a no-prompt caller through /usr/bin/security. Walking through it:

Under the default securityFramework read strategy (the strategy in the #1975 report), the CLI reader is inert: readRawClaudeKeychainPayloadViaSecurityCLIIfEnabled early-returns at guard shouldPreferSecurityCLIKeychainRead(...), which is only true for .securityCLIExperimental. So the .never missing-file repair this PR enables reaches the token exclusively through the enforced no-UI KeychainNoUIQuery path — no subprocess, no prompt.

Under the .securityCLIExperimental strategy, the experimental reader already runs under .never on main, independent of this PR: ClaudeOAuthKeychainPromptPreference.current() resolves through effectiveMode(readStrategy:), which returns .always for any non-securityFramework strategy (isApplicable is false). So the background silent paths (hasClaudeKeychainCredentialsWithoutPrompt, repair, sync) see .always at the gate and invoke the CLI reader on main today. This is covered and asserted by an existing test — experimental reader ignores prompt policy and cooldown for background silent check — which sets .securityCLIExperimental + .never + .background and expects the security-CLI read to succeed. So the experimental reader deliberately ignoring the .never policy is intended, pre-existing behavior, not something this PR introduces.

I did initially try the suggested "skip the CLI reader when allowKeychainPrompt == false" change, but it broke 7 tests in ClaudeOAuthCredentialsStoreSecurityCLITests (including the one above) precisely because it suppressed that intended experimental-reader behavior. I've reverted that part; the PR now contains only the central gate change (.never permitting guaranteed no-UI reads) plus the securityFramework no-UI repair fix and its regression tests.

If you'd still like the experimental reader's .never behavior tightened, I think that's a separate product decision from this bug (it would change shipped, tested behavior for opted-in experimental users) — happy to open a follow-up if a maintainer wants it. Otherwise the security-boundary risk here should be resolved. Could you take another look?

@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — the P1 (experimental CLI reader under .never) doesn't hold: this PR leaves that reader untouched, and its .never behavior is pre-existing/by-design (the experimental reader ignores prompt policy and cooldown for background silent check test). I trialed the suggested "skip when allowKeychainPrompt == false" change and it broke 7 ClaudeOAuthCredentialsStoreSecurityCLITests, so I reverted it. Reasoning in the comment above; PR body updated with the design rationale and before/after.

@clawsweeper

clawsweeper Bot commented Jul 12, 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.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jul 12, 2026
@OfficialAbhinavSingh
OfficialAbhinavSingh force-pushed the fix-claude-keychain-never-noui-fallback branch from 316f969 to db7bf21 Compare July 12, 2026 22:05
@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. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Jul 12, 2026
@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor Author

Verification update — real-macOS evidence + honest scope

I ran this on real hardware (arm64e, macOS 14) and want to lay out precisely what is proven and where the boundary is, rather than overclaim.

1. Deterministic before/after (the core of the fix)

The regression test reproduces the reporter's exact failure and the patch fixes it:

  • On main: never mode repairs a missing credentials file … fails with ClaudeOAuthCredentialsError.notFound — the same "Claude OAuth credentials not found" the issue reports.
  • With this change: it passes, resolving source = .claudeKeychain, owner = .claudeCLI, and logging Claude keychain credentials loaded without prompt — no UI.

This ran live (not just CI): the full ClaudeOAuthCredentialsStoreSecurityCLITests (17) and …MCPOnlyGuardTests suites stay green alongside it, so the experimental reader is unaffected.

2. Real-.never end-to-end confirms the gate change fires

With the real claudeOAuthKeychainPromptMode = never preference set (via defaults, in a real login session), a missing ~/.claude/.credentials.json, and securityFramework strategy, the debug flow shows the no-UI read actually executing:

… allowKeychainPrompt=false interactive=false … service=Claude Code-credentials  Claude keychain legacy data read start
… interactive=false … service=Claude Code-credentials status=… Claude keychain legacy data read result

On main this read never startsshouldAllowClaudeCodeKeychainAccess(.never) returns false, so loadClaudeKeychainLegacyData returns at its guard before any SecItemCopyMatching. Seeing legacy data read start under real .never is the whole behavioral change: .never now means "no interactive prompts", not "no Keychain access at all", while the interactive readers stay fully blocked.

3. Where I stopped, and why (transparency)

I did not produce a full synthetic end-to-end recovery PASS against a real Keychain, for two honest reasons:

  • The reader's query searches the default keychain by service. On the test machine a real Claude Code-credentials item is present, so my verify script (Scripts/verify_1975_live.sh) deliberately aborts before planting/deleting anything rather than risk a real credential — it never touches real OAuth material.
  • Reading that real item end-to-end requires the signed app: its ACL is scoped to the claude CLI's code signature, so an unsigned local swift build CLI gets errSecInteractionNotAllowed on it regardless of this change. That's a code-signing boundary, not a logic gap — and only reproducible cleanly in a signed/notarized context where the user has granted access.

Scripts/verify_1975_live.sh stands the scenario up safely (disposable HOME, .never set-and-restored, a synthetic -A item planted only when no real one exists, restored on exit). Run from the signed app context it should show the full green recovery; here it correctly reports SKIPPED rather than go near real credentials.

Happy to adjust the test shape or add coverage if you'd prefer a different proof surface.

@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — the verification comment above adds real-macOS evidence (deterministic before/after + the no-UI read now executing under real .never) and explains the code-signing boundary on full end-to-end recovery. Please re-assess the needs proof gate.

@clawsweeper

clawsweeper Bot commented Jul 13, 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.

Re-review progress:

@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 13, 2026
@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor Author

@steipete — a quick note on where this stands, since the remaining gate is now on your side rather than mine.

ClawSweeper's one open ask is "complete real signed-runtime before-and-after recovery." I've taken that as far as an external contributor can:

  • Deterministic before/after: the repair test fails on main with the reporter's exact .notFound and passes with this change (source = .claudeKeychain, no prompt).
  • Real macOS: under the real .never preference, the no-UI Keychain read now executes (allowKeychainPrompt=false → legacy data read start → SecItemCopyMatching), where main returns at the gate before any read.

The last step — end-to-end recovery from a real Claude Code-credentials item — I can't reproduce from a fork: an unsigned swift build CLI hits errSecInteractionNotAllowed on that item because its ACL is bound to a code signature I don't have. That's a signing boundary, not a logic gap, so it's only reproducible in your signed context.

If it's useful, Scripts/verify_1975_live.sh produces exactly that signed before/after in one command. It's non-destructive: disposable HOME, sets .never and restores it, and aborts rather than touch a real credential — it only plants a synthetic -A item when no real one exists, and deletes it on exit. Run from the signed app/context it should show the full green recovery.

One other thing on your side: as a first-time contributor here, CI is gated behind your workflow approval (only GitGuardian has run so far).

Happy to reshape the test or proof surface however you'd prefer — just let me know.

@devYRPauli devYRPauli left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. I read through the post-change source at each call site rather than the description, and the .never no-prompt boundary holds.

The key invariant - under .never, no interactive Keychain read is ever permitted - is preserved because the three interactive entry points (loadFromClaudeKeychain, loadFromClaudeKeychainWithPromptIfAllowed, and the security-framework fallback decision) keep the default allowKeychainPrompt: true, so shouldAllowClaudeCodeKeychainAccess returns !true == false for them under .never and they stay blocked. Only the guaranteed no-UI paths (missing-file repair, freshness sync, fingerprint/candidate probes) pass allowKeychainPrompt: false. The guard self.keychainAccessAllowed kill switch is untouched and still short-circuits before the mode switch.

What makes it safe rather than just plausibly safe is that the same allowKeychainPrompt drives both the policy check and the actual query: loadClaudeKeychainData applies KeychainNoUIQuery.apply(&query) when !allowKeychainPrompt (ClaudeOAuthCredentials.swift ~L2071), which sets kSecUseAuthenticationUI = kSecUseAuthenticationUIFail. So passing false produces a query that fails instead of prompting - the policy gate and the query construction can't drift apart.

I built the PR head and ran the new suite locally: all 3 tests pass, including never mode still blocks an interactive Keychain read even with a valid item present, which covers the negative direction of the boundary (a valid item is present, prompt requested, still throws .notFound under .never) - not just the happy-path repair. The logic diff is minimal (+40/-17 in one file, a defaulted parameter plus mechanical call-site threading); the rest is the test file and a standalone verification script.

Two small notes, neither blocking:

  • Scripts/verify_1975_live.sh temporarily writes the real com.steipete.codexbar defaults domain (claudeOAuthKeychainPromptMode) during a live run and restores it via a trap. Non-destructive, but worth flagging in case you'd rather a verification script not touch real user prefs.
  • This is really the security-framing decision that was flagged for maintainer sign-off: the implementation correctly keeps interactive reads blocked under .never and only newly permits guaranteed no-UI reads, so the behavior change is "avoid prompts" continuing to mean "never show a prompt" rather than "never touch the Keychain." That reading looks right to me, but the policy call is yours.

@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor Author

Thanks @devYRPauli — appreciate you reading it call-site by call-site and running the suite rather than trusting the description. You've restated the invariant exactly: the three interactive entry points keep allowKeychainPrompt: true and stay blocked under .never; only the guaranteed no-UI paths pass false; and since the same flag drives both the policy check and KeychainNoUIQuery.apply, the gate and the query can't drift apart. That last part was the design goal, so it's good to see it read that way independently.

On your two notes:

  1. The real-prefs touch is structural for the live check, unfortunately. ClaudeOAuthKeychainPromptPreference reads the mode via UserDefaults(suiteName: "com.steipete.codexbar"), which cfprefsd serves per-uid — CFFIXED_USER_HOME can't redirect it, so a fixture-home write isn't seen by the spawned CLI. That's why the script writes the real domain and restores the prior value (or deletes the key if it was unset) via an EXIT trap. If you or @steipete would rather a casual run never touch real prefs, I'm happy to gate that Phase-2 write behind an explicit opt-in flag (e.g. CODEXBAR_1975_LIVE_E2E=1) so it's off by default — just say the word and I'll push it.

  2. Agreed the crux is the .never = "never show a prompt" vs "never touch the Keychain" reading, and that the policy call is @steipete's. The PR body isolates exactly that as the one thing to sign off, and the keychainAccessAllowed kill switch is left untouched ahead of the mode switch so "disable Keychain access" still hard-blocks everything.

Thanks again for the thorough pass.

Co-authored-by: OfficialAbhinavSingh <abhinav.25bcs10345@sst.scaler.com>
@steipete
steipete force-pushed the fix-claude-keychain-never-noui-fallback branch from 0955012 to ad3a67c Compare July 16, 2026 10:44
@steipete steipete changed the title Fix Claude never-prompt no-UI Keychain repair for missing credentials file (#1975) Fix Claude no-prompt Keychain repair Jul 16, 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: ad3a67cfea

ℹ️ 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 +616 to +618
guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(
mode: mode,
allowKeychainPrompt: false) 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 Require a usable no-UI Keychain read in Never mode

When the stored mode is Never, this guard now lets the method continue, but the status switch below still treats errSecInteractionNotAllowed as success. ClaudeProviderDescriptor.isPlausiblyAvailable relies on this method before fetching, so a Claude Keychain item that requires UI is now advertised as available even though the later credential load runs with allowKeychainPrompt: false and cannot read it. In Never mode, verify the secret can be loaded noninteractively or treat interaction-required results as unavailable.

Useful? React with 👍 / 👎.

Comment on lines +795 to +796
guard ClaudeOAuthCredentialsStore
.shouldAllowClaudeCodeKeychainAccess(mode: mode, allowKeychainPrompt: false) else { return nil }

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 Bypass prompt cooldown for Never-mode no-UI repair

This opens Never mode to the repair path, but the existing cooldown check below still runs for normal background loads because loadRecord derives respectKeychainPromptCooldown from !allowKeychainPrompt. If a user has a persisted denied-prompt cooldown and then relies on Never-mode no-UI repair, shouldAllowPrompt is false and the repair returns nil even though the subsequent Keychain reads are KeychainNoUIQuery reads that cannot show a prompt. Skip the prompt cooldown for the .never/noninteractive repair case.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Owner

Maintainer proof for exact head ad3a67cfea6f9aace45cfc12039df8ebe68f657f:

  • Reworked the change so Never blocks every prompt-capable path, including /usr/bin/security, while retaining only the Security.framework query guarded by kSecUseAuthenticationUIFail.
  • Added fixture contracts for no-UI recovery, interactive-read rejection, absent-item failure, global-disable behavior, and the experimental CLI-reader boundary: 5/5 passed.
  • Focused affected suites: 59/59 passed.
  • Full local suite: 652/652 selections passed first attempt; 0 retries, failures, or timeouts.
  • make check: passed (SwiftFormat, SwiftLint, package/docs/repository gates).
  • Autoreview: clean after centralizing the prompt-policy guard.
  • Hosted exact-head CI: https://github.com/steipete/CodexBar/actions/runs/29491895280

Thanks @OfficialAbhinavSingh for finding and fixing this recovery gap.

@steipete
steipete merged commit c47a20a into steipete:main Jul 16, 2026
8 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: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. 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

3 participants