Skip to content

fix: cache third-party keychain data to eliminate repeated password prompts#114

Merged
kargnas merged 1 commit intomainfrom
fix/keychain-prompt-caching
Apr 1, 2026
Merged

fix: cache third-party keychain data to eliminate repeated password prompts#114
kargnas merged 1 commit intomainfrom
fix/keychain-prompt-caching

Conversation

@kargnas
Copy link
Copy Markdown
Member

@kargnas kargnas commented Apr 1, 2026

Summary

  • Cache browser encryption keys (Chrome/Brave/Arc/Edge Safe Storage), Claude Code credentials, and Copilot CLI tokens in app-owned keychain items after the first successful read
  • App-owned keychain items use Developer ID-based ACL which persists across app updates, unlike third-party items whose partition_id resets on token refresh
  • Wire 401/403 error handling in ClaudeProvider and CopilotProvider to invalidate both persistent keychain cache and in-memory cache, enabling automatic recovery on next fetch cycle

Problem

macOS prompts for keychain password daily because:

  1. Third-party keychain items (Chrome Safe Storage, Claude Code, Copilot CLI) use partition_id ACL which resets when the original app refreshes tokens
  2. After app updates via Sparkle, the binary hash changes and macOS re-evaluates trust for third-party items

Solution

Cache-first strategy: Read third-party keychain once → cache in our own keychain → all subsequent reads use cache (no prompt). Self-healing when keys/tokens change:

Access Point File Cache Key
Browser Safe Storage BrowserCookieService.swift com.copilotmonitor.BrowserEncryptionKeyCache
Claude Code credentials TokenManager.swift com.copilotmonitor.KeychainDataCache
Copilot CLI accounts TokenManager.swift com.copilotmonitor.KeychainDataCache

Cache invalidation paths

  • Browser key rotation: Detects decryption failure → compares fresh key with cached → only re-prompts if key actually changed
  • Claude 401: ClaudeProvider calls invalidateClaudeKeychainCaches() → clears persistent + memory cache
  • Copilot 401/403: CopilotProvider calls invalidateCopilotKeychainCaches() → clears persistent + memory cache
  • Cache parse failure: readKeychainJSON falls through to original keychain if cached data can't be parsed

Changed Files

  • BrowserCookieService.swift — Encryption key cache layer with stale-key detection
  • TokenManager.swift — Keychain data cache helpers, Copilot CLI account serialization cache, invalidation methods
  • ClaudeProvider.swift — Invalidate Claude keychain cache on 401
  • CopilotProvider.swift — Invalidate Copilot keychain cache on 401/403

…te repeated password prompts

Cache browser encryption keys (Chrome/Brave/Arc/Edge Safe Storage),
Claude Code credentials, and Copilot CLI tokens in our own keychain
items after the first successful read. App-owned keychain items use
Developer ID-based ACL which persists across app updates, unlike
third-party items whose partition_id can reset on token refresh.

Key changes:
- BrowserCookieService: cache encryption keys with stale-key detection
  and automatic re-fetch when browser rotates its key
- TokenManager: cache readKeychainJSON and Copilot CLI account data
  with fallback to original keychain on cache parse failure
- ClaudeProvider/CopilotProvider: invalidate keychain + memory caches
  on 401/403 so stale tokens auto-recover on next fetch cycle

Co-authored-by: opencode (Sisyphus, oMo) <no-reply@opencode.ai>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@kargnas kargnas merged commit 674ef40 into main Apr 1, 2026
10 of 11 checks passed
@kargnas kargnas deleted the fix/keychain-prompt-caching branch April 1, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant