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)
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
- Use Claude Code via the VS Code extension on macOS with subscription auth over several days (entry created ~6 days before symptoms)
- Let a token refresh rewrite the keychain item (observable:
mdatchanges,cdatdoesn't) - Submit any prompt → SecurityAgent dialog appears; enter password + "Always Allow"
- Dialog appears to ignore input (in reality: next dialog of the stack)
- Open a new session → login screen appears despite valid stored credentials
- 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_
3 Comments
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.
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: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: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:
mdaton the item is exactly20260828084243Z,cdatunchanged since June, so it is an in-place rewrite, consistent with what this issue already documents. Theclaudeprocess 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:
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.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 bycdhash: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.
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):The user's "Always Allow" from 07:26 survived only ~2h43m, killed by the 10:09:42 write. That write is the
claudeCLI itself refreshing its own token by shelling out tosecurity add-generic-password -U -a … -s "Claude Code-credentials" …, which goes through the legacySecKeychainItemModifyContentpath rather than the partition-awareSecItemUpdate. 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 oneis 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:
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.)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.SecItemCopyMatchingwithkSecReturnAttributes(nokSecReturnData) never triggers this prompt at all — only the value read + the writer's ownSecKeychainItemModifyContentcall do. This matches thesecurity dump-keychain/cdat-mdatevidence already in this issue's own writeup.Suggested fix, concretely: switch the CLI's own keychain-write path for
Claude Code-credentialsfromsecurity add-generic-password -U(→SecKeychainItemModifyContent) toSecItemUpdate, 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.