Endless SecurityAgent prompt stack for "Claude Code-credentials": credential rewrite creates keychain partition mismatch, "Always Allow" can never persist (behavior persists after #41026 was closed as resolved)

Status Open
Reported on v2.1.227
Maintainer reply None cached
Activity 3 comments · opened Aug 17, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Recurring SecurityAgent dialogs ("security wants to access 'Claude Code-credentials'") that "Always Allow" can never silence, and new sessions falling back to the login screen while an existing session keeps working. This is the behavior from #41026, which is closed/locked as resolved — it still occurs in 2.1.227. Root-cause analysis from today's debugging:

1. The "frozen" dialog is actually a stack. Each prompt submission triggers a credential read via a short-lived /usr/bin/security child process. Blocked reads queue up; each gets its own pixel-identical dialog. Answering one reveals the next — which looks exactly like a frozen dialog ignoring input. Clicking "Deny" ~20 times drained the stack (harmless; the running session keeps using its in-memory token).

2. "Always Allow" cannot persist: keychain partition mismatch. security dump-keychain -a showed the long-lived entry under the app's team-ID partition (teamid:Q6L2SF6YDW), while reads go through /usr/bin/security (partition apple-tool:). Cross-partition reads always prompt; "Always Allow" does not heal a partition conflict. cdat/mdat analysis confirmed the token refresh rewrites the item in place and resets its ACL each time.

3. Manual repairs lose a race against the async token write. The binary writes the keychain item asynchronously, seconds to minutes after login completes. Any manual repair (e.g. the set-generic-password-partition-list workaround from #41026) that runs inside that window is silently overwritten.

Reliable recovery (verified): drain the dialog stack with "Deny" → delete ALL matching items (while security delete-generic-password -s "Claude Code-credentials" >/dev/null 2>&1; do :; done) → log in once fresh. The binary recreates the item via the security CLI itself, so creator, partition (apple-tool:) and reader finally match — every read is silent afterwards, across new sessions.

Suggested fix: on every credential (re)write, set a consistent partition list and trusted-app ACL (or stop resetting the ACL on refresh). The mixed-partition state seems to arise when entries created by older versions (or CLI vs. extension binary) are later rewritten in place instead of recreated cleanly.

What Should Happen?

"Always Allow" should persist across token refreshes; credential reads should be silent after a one-time grant; new sessions should reuse the stored credentials without demanding a re-login; and manual keychain repairs should not be silently overwritten by an async rewrite.

Error Messages/Logs

SecurityAgent dialog (repeating, identical): security wants to access "Claude Code-credentials" in your keychain.

Evidence from the broken entry (before recovery):
- `security dump-keychain -a`: partition_id entry shows `teamid:Q6L2SF6YDW` regime on the stored item, while the reader is `/usr/bin/security` (`apple-tool:`)
- `cdat` unchanged for days, `mdat` updated at each token refresh → in-place rewrite resetting the ACL

After recovery (working state): item's partition_id shows `apple-tool:`; reads silent. Sanitized dumps available on request.

Steps to Reproduce

  1. Use Claude Code via the VS Code extension on macOS with subscription auth over several days (entry created ~6 days before symptoms)
  2. Let a token refresh rewrite the keychain item (observable: mdat changes, cdat doesn't)
  3. Submit any prompt → SecurityAgent dialog appears; enter password + "Always Allow"
  4. Dialog appears to ignore input (in reality: next dialog of the stack)
  5. Open a new session → login screen appears despite valid stored credentials
  6. Apply the partition-list workaround from #41026 right after login → overwritten by the async token write, symptoms return

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.227 (VS Code extension native binary; no standalone CLI installed)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

3 Comments

Sar3k2024 · 10 days ago

Cross-referencing #88024 (filed 2026-08-19): it reports that Claude Code shells out to the security CLI by bare name (PATH-resolved) for every Keychain read/write, instead of calling /usr/bin/security absolutely.

That is the same underlying code path as this issue. The partition mismatch documented here exists because credential reads go through a shelled-out security child process (→ apple-tool: partition) while the credential item gets rewritten under a different regime. Any fix for either issue touches the same call site, so it may be worth triaging them together:

#87348 (this issue): the shelled-out security read is cross-partition against the rewritten item → endless SecurityAgent prompt stack; "Always Allow" can never persist across a partition boundary.
#88024: the shelled-out call itself is PATH-resolved, so a shadowed security binary breaks auth silently — and is a PATH-hijacking risk for a credential-access operation.
Both would be resolved more robustly by moving Keychain access to the native Security.framework APIs — that would also give the credential item a stable creator/partition, making "Always Allow" actually persist. Short of that, absolute-pathing /usr/bin/security (fixes #88024) plus keeping creator and reader in the same partition regime (fixes this issue) would address both.

0x1337ff · 2 days ago

Same root cause, but a variant this thread has not covered yet: the rewrite also evicts third-party apps the user explicitly authorized, and it does so silently because the ACL still looks correct.

Measured this morning on the standalone CLI (2.1.247, Homebrew cask, Developer ID Application: Anthropic PBC (Q6L2SF6YDW)), macOS 26 / Darwin 25.6.0, arm64.

The item's partition list before and after a token refresh, read via SecKeychainItemCopyAccess / SecAccessCopyACLList:

before  ->  partitions : ['apple-tool:', 'cdhash:7a837ef70fd2988988965adb7631edba8e6ae844']
after   ->  partitions : ['apple-tool:']

The cdhash: entry is the third-party reader (PokeTokenBar, a menu-bar app that displays token usage, authorized once by the user through "Always Allow"). It is gone after the refresh. Meanwhile the ACL's trusted-application list is untouched:

[0] 'Claude Code-credentials'  -> 2 applications:
     - /Applications/PokeTokenBar.app
     - /usr/bin/security

That asymmetry is what makes this so confusing to diagnose: the app is still listed as authorized, so the user assumes the third-party app is at fault, while macOS prompts because the partition no longer matches.

Attribution of the write, from the unified log:

10:42:43.219  security[66933] (Security) SecKeychainItemModifyContent
10:42:42.742  mDNSResponder: DNSServiceGetAddrInfo START -- client pid: <pid> (claude)
10:42:43.291  mDNSResponder: DNSServiceGetAddrInfo START -- client pid: <pid> (claude)

mdat on the item is exactly 20260828084243Z, cdat unchanged since June, so it is an in-place rewrite, consistent with what this issue already documents. The claude process was doing its OAuth network round-trip in the same second.

Frequency, from the third-party app's own log: 5 lockouts in 48 hours, each one ended only by the user re-entering the keychain password, never by an automatic recovery. Roughly one every 5 to 8 hours, matching the token lifetime.

Two control tests, so the blame is not misplaced:

  • Replaying security add-generic-password -U -a … -s … -X … by hand on a throwaway item preserves both the ACL application list and the partition list. So the plain update is not inherently destructive; the loss is specific to how the credential item is rewritten here.
  • A purpose-built ad-hoc-signed binary, added to a test item's ACL, reads it successfully with the no-UI query flags (LAContext.interactionNotAllowed + kSecUseAuthenticationUIFail). So the third-party app's silent-read path is sound; it fails only because it has been removed from the partition.

Why the recovery documented above does not help this case: deleting the items and logging in fresh realigns Claude Code's own reader with apple-tool:, which is why reads go silent for the reporter. A third-party app is identified by cdhash: instead, so it is evicted again at the very next refresh. There is no user-side repair that survives, which makes the suggested fix in this issue - set a consistent partition list on every credential write, or stop resetting it on refresh - the only way out for this class of user.

Worth noting for triage: any user-authorized keychain client is affected, not just usage-monitor apps. Moving credential access to the in-process Security framework APIs, as suggested in the comment above, would fix this variant too.

asphoa · 1 day ago

Adding independent forensic confirmation from a completely unrelated third-party app that only reads this credential (a menu-bar quota-usage tool, not affiliated with Anthropic), which pins down the exact write-path culprit and explains why the loss of "Always Allow" isn't limited to Claude Code's own internal partition mismatch (VS Code extension vs CLI) — it also silently revokes grants held by any other local app that has nothing to do with Claude Code at all.

Mechanism, confirmed via securityd's own log (macOS, today):

07:26:23.834  integrity: ACL partition mismatch: client cdhash:4021dab6… ACL ("apple-tool:")
07:26:23.834  integrity: asking user about XARA partition for 'cdhash:4021dab6…'
07:26:23.834  kcacl: client is valid, proceeding          ← trust-program ACL passes
07:26:23.835  kcacl: displaying keychain prompt for /Applications/<third-party-app>.app
07:26:29.523  kcacl: user approved 'always allow'
07:26:29.523  integrity: adding XARA partition 'cdhash:4021dab6…' to list

10:09:42.459  security[70817]: SecKeychainItemModifyContent
10:09:42.470  security[70817]: integrity: no previous integrity acl exists; making a new one
10:09:44.278  integrity: ACL partition mismatch: client cdhash:4021dab6…
10:09:44.279  kcacl: displaying keychain prompt for /Applications/<third-party-app>.app

The user's "Always Allow" from 07:26 survived only ~2h43m, killed by the 10:09:42 write. That write is the claude CLI itself refreshing its own token by shelling out to security add-generic-password -U -a … -s "Claude Code-credentials" …, which goes through the legacy SecKeychainItemModifyContent path rather than the partition-aware SecItemUpdate. That legacy path discards the item's entire integrity ACL — including the partition list — and rebuilds a fresh one containing only the writer itself (no previous integrity acl exists; making a new one is that rebuild happening live). Any other app's prior "Always Allow" grant, third-party or Claude-Code-internal, is gone at that instant, independent of any cross-partition mismatch between Claude Code's own components.

Three corroborating checks on the same machine:

  1. cdat = an install three-plus months ago, mdat = today — the item is updated in place, never deleted/recreated, across that whole span. (Rules out "the item gets recreated" as the mechanism; it's the ACL inside the same item that gets wiped.)
  2. The item's trust-program ACL has accumulated 30 entries over time (grants persist and stack there), while the partition list has only ever had apple-tool: plus a single third-party app's cdhash — because the partition list, unlike the trust-program ACL, gets thrown away and rebuilt on every write via this code path.
  3. A read-only SecItemCopyMatching with kSecReturnAttributes (no kSecReturnData) never triggers this prompt at all — only the value read + the writer's own SecKeychainItemModifyContent call do. This matches the security dump-keychain/cdat-mdat evidence already in this issue's own writeup.

Suggested fix, concretely: switch the CLI's own keychain-write path for Claude Code-credentials from security add-generic-password -U (→ SecKeychainItemModifyContent) to SecItemUpdate, which updates an existing item's data without discarding its accumulated ACL/partition list. This is a narrower, more specific fix than "set a consistent partition list on every write" — the problem isn't which partition list gets set, it's that this particular write API call clears it every single time regardless of what it's set to.

Happy to share the sanitized full log capture and the read-only probe code if useful.