fix(menubar): stop repeated keychain prompts on token refresh (#490)#491
Merged
Conversation
…#490) Background token refreshes re-read the "Claude Code-credentials" keychain item via the Security framework. On macOS Sierra+, access is governed by the item's partition list, not the legacy "Always Allow" ACL. Claude Code resets that partition list every time it rotates the credential, dropping our app from the allowed set, so the next read raises a fresh keychain password prompt. On a heavy usage day this fires dozens of times. The LAContext interactionNotAllowed flag we relied on does not suppress that prompt for a plain generic-password item. Route the silent path (proactive refresh and post-401 re-read) through /usr/bin/security instead. The Apple-signed security binary sits in the item's apple-tool: partition, so it reads the secret without prompting and without depending on the user's ACL grant. It is read-only and never spends the shared refresh token, preserving the existing invariant that the Claude CLI owns the grant. The user-initiated bootstrap keeps the framework read, where a single consent prompt is expected. Drops the now-unused LocalAuthentication import.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #490.
Problem
The menubar re-reads the
Claude Code-credentialskeychain item on every silent token refresh (proactive near-expiry refresh and post-401 retry) via the Security framework. On macOS Sierra+, access to a generic-password item is governed by its partition list, not the legacy "Always Allow" ACL shown in Keychain Access. Claude Code resets that partition list each time it rotates its OAuth credential, which drops the menubar's code identity from the allowed set. The next read then raises a fresh keychain password prompt. On a heavy Claude Code day this fires dozens of times.The
LAContext.interactionNotAllowedflag the code relied on does not suppress the partition-list prompt for a plain generic-password item, so the "silent" path was not actually silent.Fix
Route the silent read path through
/usr/bin/security find-generic-password ... -w. The Apple-signedsecuritybinary sits in the item'sapple-tool:partition, so it reads the secret without ever prompting and without depending on the user's ACL grant. It is read-only and never spends the shared refresh token, so the existing invariant (the Claude CLI owns the grant; we never refresh it ourselves) is preserved.The user-initiated bootstrap keeps the framework read, where a single consent prompt is expected. Removes the now-unused
LocalAuthenticationimport and the ineffectiveinteractionNotAllowedbranch.Did not implement the issue's suggested Option A (refresh via the Anthropic OAuth API using the cached refresh token): Claude's refresh token is single-use and shared with the CLI, so spending it would invalidate the user's
claudelogin. Option B (this change) avoids that.Scope
Menubar app only (
mac/Sources/CodeBurnMenubar/). No CLI changes.Verification
swift buildpasses.security find-generic-password -s "Claude Code-credentials" -a "$USER" -wreturns the validclaudeAiOauthJSON with no prompt on macOS.Release
Ship as
mac-v0.9.13(not a re-upload of 0.9.12): the in-app updater only offers an update when the release version is strictly greater than the installed version, so reusing 0.9.12 would never reach existing users.