Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Kimi: reuse fresh signed-in Kimi Code CLI credentials in Auto mode without refreshing or rewriting CLI-owned authentication state. Thanks @Leechael!

### Fixed
- Claude: keep MCP-only credential discovery out of background Security.framework reads when Keychain prompts are limited to user actions, preventing recurring macOS password dialogs (#2115).
- Claude: cache successful CLI version probes for 30 minutes while invalidating on executable changes, avoiding repeated PTY launches without retaining failed or stale wrapper results. Thanks @Yuxin-Qiao!
- Linux CLI: bootstrap the configured IANA timezone before Foundation startup on non-FHS systems, preventing SIGILL on NixOS (#2127). Thanks @xikhar!
- Ollama: release temporary dashboard network sessions after each fetch, preventing repeated refreshes from retaining delegates and URL-cache resources. Thanks @astuteprogrammer!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,17 @@ extension ClaudeOAuthCredentialsStore {
guard !keychainAccessDisabled || self.isolatedSecurityCLIKeychainPath(environment: environment) != nil else {
return false
}
guard ClaudeOAuthKeychainPromptPreference.effectiveMode(readStrategy: readStrategy) != .never else {
let promptMode = ClaudeOAuthKeychainPromptPreference.effectiveMode(readStrategy: readStrategy)
guard promptMode != .never else {
return false
}
// Security.framework "no UI" queries can still display the legacy Keychain ACL password dialog on some
// systems. Do not touch the Claude Code keychain from background discovery when the stored policy only
// permits user-initiated access. Explicit refreshes retain MCP-only detection, and `.always` remains opt-in.
if readStrategy == .securityFramework,
promptMode == .onlyOnUserAction,
interaction != .userInitiated
{
return false
}
let payload: Data? = switch readStrategy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Testing
@Suite(.serialized)
struct ClaudeOAuthCredentialsStoreMCPOnlyGuardTests {
@Test
func `standard reader blocks expired CLI owner in background but preserves user refresh`() async throws {
func `standard reader skips MCP keychain probe in background but preserves user refresh`() async throws {
let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)"
let mcpOAuthOnly = Data(#"{"mcpOAuth":{"plugin:test":{"accessToken":"synthetic"}}}"#.utf8)
let tempDir = FileManager.default.temporaryDirectory
Expand Down Expand Up @@ -37,7 +37,17 @@ struct ClaudeOAuthCredentialsStoreMCPOnlyGuardTests {
keychainAccessDisabled: false,
environment: [:])
}
#expect(isMcpOnly)
#expect(!isMcpOnly)

let userInitiatedIsMcpOnly = ProviderInteractionContext.$current
.withValue(.userInitiated) {
ClaudeOAuthCredentialsStore.isMcpOAuthOnlyClaudeKeychainPayloadPresent(
interaction: .userInitiated,
readStrategy: .securityFramework,
keychainAccessDisabled: false,
environment: [:])
}
#expect(userInitiatedIsMcpOnly)

ClaudeOAuthCredentialsStore.invalidateCache()
let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)
Expand All @@ -54,10 +64,10 @@ struct ClaudeOAuthCredentialsStoreMCPOnlyGuardTests {
environment: [:],
allowKeychainPrompt: false,
respectKeychainPromptCooldown: true)
Issue.record("Expected MCP-only keychain failure")
Issue.record("Expected background refresh delegation")
} catch let error as ClaudeOAuthCredentialsError {
guard case .mcpOAuthOnlyKeychain = error else {
Issue.record("Expected .mcpOAuthOnlyKeychain, got \(error)")
guard case .refreshDelegatedToClaudeCLI = error else {
Issue.record("Expected .refreshDelegatedToClaudeCLI, got \(error)")
return
}
} catch {
Expand Down
Loading