-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Fix Claude no-prompt Keychain repair #2102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -613,15 +613,20 @@ public enum ClaudeOAuthCredentialsStore { | |
| self.context.run { | ||
| #if os(macOS) | ||
| let mode = ClaudeOAuthKeychainPromptPreference.current() | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return false } | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess( | ||
| mode: mode, | ||
| allowKeychainPrompt: false) else { return false } | ||
| if ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled( | ||
| interaction: ProviderInteractionContext.current) != nil | ||
| { | ||
| return true | ||
| } | ||
|
|
||
| let fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode() | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode) else { | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess( | ||
| mode: fallbackPromptMode, | ||
| allowKeychainPrompt: false) | ||
| else { | ||
| return false | ||
| } | ||
| if ProviderInteractionContext.current == .background, | ||
|
|
@@ -707,7 +712,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| { | ||
| #if os(macOS) | ||
| let mode = ClaudeOAuthKeychainPromptPreference.current() | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil } | ||
| guard ClaudeOAuthCredentialsStore | ||
| .shouldAllowClaudeCodeKeychainAccess(mode: mode, allowKeychainPrompt: false) else { return nil } | ||
| if ClaudeOAuthCredentialsStore.isPromptPolicyApplicable, | ||
| respectKeychainPromptCooldown, | ||
| !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now) | ||
|
|
@@ -786,7 +792,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| { | ||
| #if os(macOS) | ||
| let mode = ClaudeOAuthKeychainPromptPreference.current() | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil } | ||
| guard ClaudeOAuthCredentialsStore | ||
| .shouldAllowClaudeCodeKeychainAccess(mode: mode, allowKeychainPrompt: false) else { return nil } | ||
|
Comment on lines
+795
to
+796
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This opens Never mode to the repair path, but the existing cooldown check below still runs for normal background loads because Useful? React with 👍 / 👎. |
||
|
|
||
| if ClaudeOAuthCredentialsStore.shouldShowClaudeKeychainPreAlert() { | ||
| return nil | ||
|
|
@@ -897,7 +904,9 @@ public enum ClaudeOAuthCredentialsStore { | |
| self.context.run { | ||
| #if os(macOS) | ||
| let mode = ClaudeOAuthKeychainPromptPreference.current() | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return false } | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess( | ||
| mode: mode, | ||
| allowKeychainPrompt: false) else { return false } | ||
|
|
||
| if let data = ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled( | ||
| interaction: ProviderInteractionContext.current), | ||
|
|
@@ -916,7 +925,10 @@ public enum ClaudeOAuthCredentialsStore { | |
| } | ||
|
|
||
| let fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode() | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode) else { | ||
| guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess( | ||
| mode: fallbackPromptMode, | ||
| allowKeychainPrompt: false) | ||
| else { | ||
| return false | ||
| } | ||
|
|
||
|
|
@@ -1641,7 +1653,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| return .value(override) | ||
| } | ||
| #endif | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return .unavailable } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode, allowKeychainPrompt: false) | ||
| else { return .unavailable } | ||
| if self.isPromptPolicyApplicable, | ||
| ProviderInteractionContext.current == .background, | ||
| !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt() | ||
|
|
@@ -1704,8 +1717,9 @@ public enum ClaudeOAuthCredentialsStore { | |
| return data | ||
| } | ||
|
|
||
| // For experimental strategy, enforce stored prompt policy before any Security.framework fallback probes. | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode) else { return nil } | ||
| // For experimental strategy, apply the stored policy before no-UI Security.framework fallback probes. | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode, allowKeychainPrompt: false) | ||
| else { return nil } | ||
|
|
||
| #if DEBUG | ||
| if let store = taskClaudeKeychainOverrideStore { | ||
|
|
@@ -1901,7 +1915,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| enforcePromptPolicy: Bool = true) -> ClaudeKeychainProbe<[ClaudeKeychainCandidate]> | ||
| { | ||
| if enforcePromptPolicy { | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return .unavailable } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode, allowKeychainPrompt: false) | ||
| else { return .unavailable } | ||
| if self.isPromptPolicyApplicable, | ||
| ProviderInteractionContext.current == .background, | ||
| !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt() | ||
|
|
@@ -1970,7 +1985,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| enforcePromptPolicy: Bool = true) -> ClaudeKeychainProbe<ClaudeKeychainCandidate?> | ||
| { | ||
| if enforcePromptPolicy { | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return .unavailable } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode, allowKeychainPrompt: false) | ||
| else { return .unavailable } | ||
| if self.isPromptPolicyApplicable, | ||
| ProviderInteractionContext.current == .background, | ||
| !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt() | ||
|
|
@@ -2028,7 +2044,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| allowKeychainPrompt: Bool, | ||
| promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current()) throws -> Data? | ||
| { | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return nil } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode, allowKeychainPrompt: allowKeychainPrompt) | ||
| else { return nil } | ||
| self.log.debug( | ||
| "Claude keychain data read start", | ||
| metadata: [ | ||
|
|
@@ -2090,7 +2107,8 @@ public enum ClaudeOAuthCredentialsStore { | |
| allowKeychainPrompt: Bool, | ||
| promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current()) throws -> Data? | ||
| { | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return nil } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode, allowKeychainPrompt: allowKeychainPrompt) | ||
| else { return nil } | ||
| self.log.debug( | ||
| "Claude keychain legacy data read start", | ||
| metadata: [ | ||
|
|
@@ -2341,11 +2359,16 @@ public enum ClaudeOAuthCredentialsStore { | |
| } | ||
|
|
||
| private static func shouldAllowClaudeCodeKeychainAccess( | ||
| mode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current()) -> Bool | ||
| mode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current(), | ||
| allowKeychainPrompt: Bool = true) -> Bool | ||
| { | ||
| guard self.keychainAccessAllowed else { return false } | ||
| switch mode { | ||
| case .never: return false | ||
| case .never: | ||
| // `.never` means "no interactive prompts", not "no Keychain access at all": a guaranteed | ||
| // no-UI read (KeychainNoUIQuery) must still be able to repair a missing credentials file | ||
| // from a valid Keychain item without ever surfacing a system prompt. | ||
| return !allowKeychainPrompt | ||
| case .onlyOnUserAction: | ||
| return ProviderInteractionContext.current == .userInitiated || self.allowBackgroundPromptBootstrap | ||
| case .always: return true | ||
|
|
@@ -2365,7 +2388,7 @@ public enum ClaudeOAuthCredentialsStore { | |
| #endif | ||
| #if os(macOS) | ||
| let mode = ClaudeOAuthKeychainPromptPreference.current() | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode, allowKeychainPrompt: false) else { return nil } | ||
| // Keep experimental mode prompt-safe: avoid Security.framework candidate probes when preflight says | ||
| // interaction is likely. | ||
| if self.shouldShowClaudeKeychainPreAlert() { | ||
|
|
@@ -2484,7 +2507,7 @@ extension ClaudeOAuthCredentialsStore { | |
| } | ||
| #endif | ||
| let mode = ClaudeOAuthKeychainPromptPreference.current() | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return false } | ||
| guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode, allowKeychainPrompt: false) else { return false } | ||
| return switch KeychainAccessPreflight.checkGenericPassword(service: self.claudeKeychainService, account: nil) { | ||
| case .interactionRequired: | ||
| true | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the stored mode is Never, this guard now lets the method continue, but the status switch below still treats
errSecInteractionNotAllowedas success.ClaudeProviderDescriptor.isPlausiblyAvailablerelies 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 withallowKeychainPrompt: falseand cannot read it. In Never mode, verify the secret can be loaded noninteractively or treat interaction-required results as unavailable.Useful? React with 👍 / 👎.