docs/KEYCHAIN_FIX.md
The original fix (migrating legacy CodexBar keychain items to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) is
still in place, but the architecture has changed:
~/.codexbar/config.json.com.steipete.codexbar.cache) and Claude OAuth
bootstrap reads from Claude CLI keychain (Claude Code-credentials).| Previous statement in this doc | Current behavior |
|---|---|
| CodexBar stores provider credentials only in keychain | Manual/provider settings are config-file backed (~/.codexbar/config.json), while keychain is still used for runtime caches and Claude OAuth bootstrap fallback. |
ClaudeOAuthCredentials.swift migrated CodexBar-owned Claude OAuth keychain items | Claude OAuth primary source is Claude CLI keychain service (Claude Code-credentials), with CodexBar cache in com.steipete.codexbar.cache (oauth.claude). |
Migration runs in CodexBarApp.init() | Migration runs in HiddenWindowView .task via detached task (KeychainMigration.migrateIfNeeded()). |
| Post-migration prompts should be zero in all Claude paths | Legacy-store prompts are reduced; Claude OAuth bootstrap can still prompt when reading Claude CLI keychain, with cooldown + no-UI probes to prevent storms. |
Log category is KeychainMigration | Category is keychain-migration (kebab-case). |
Sources/CodexBar/KeychainMigration.swift migrates legacy com.steipete.CodexBar items (for example
claude-cookie) to AfterFirstUnlockThisDeviceOnly.
KeychainMigrationV1CompletedSources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift
Load order for credentials:
CODEXBAR_CLAUDE_OAUTH_TOKEN, scopes env key).com.steipete.codexbar.cache, account oauth.claude).~/.claude/.credentials.json.Claude Code-credentials (promptable fallback).Prompt mitigation:
KeychainNoUIQuery with LAContext.interactionNotAllowed.claudeOAuthKeychainDeniedUntil
(ClaudeOAuthKeychainAccessGate). User actions (menu open / manual refresh) clear this cooldown.syncWithClaudeKeychainIfChanged)
and can update cached OAuth data when the token changes.When CodexBar does not have usable OAuth credentials in its own cache (com.steipete.codexbar.cache / oauth.claude),
bootstrap falls through to Claude CLI keychain reads.
Current flow can perform up to two interactive reads in one bootstrap call:
On some macOS keychain/ACL states, pressing Allow (session-only) for the first read does not grant enough access for the second read shape, so macOS prompts again. Pressing Always Allow usually authorizes both query shapes for the app identity and avoids the immediate second prompt.
The prompt copy differs because Security.framework is authorizing different operations:
This is OS/keychain ACL behavior, not a ThisDeviceOnly migration issue.
Sources/CodexBarCore/CookieHeaderCache.swift and Sources/CodexBarCore/KeychainCacheStore.swift
com.steipete.codexbar.cache.cookie.claude.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.codexbar cache clear --cookies. --provider <id> scopes cookie clearing to one provider and includes scoped
Codex managed-account cookie keys.ThisDeviceOnlyCookieHeaderStore, token stores, MiniMax stores) still write using
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.com.steipete.codexbar.cache) also writes with ThisDeviceOnly.Advanced -> Disable Keychain access sets debugDisableKeychainAccess and flips KeychainAccessGate.isDisabled.
Effects:
defaults read com.steipete.codexbar KeychainMigrationV1Completed
defaults read com.steipete.codexbar claudeOAuthKeychainDeniedUntil
log show --predicate 'subsystem == "com.steipete.codexbar" && (category == "keychain-migration" || category == "keychain-preflight" || category == "keychain-prompt" || category == "keychain-cache" || category == "claude-usage" || category == "cookie-cache")' --last 10m
defaults delete com.steipete.codexbar KeychainMigrationV1Completed
./Scripts/compile_and_run.sh
Sources/CodexBar/KeychainMigration.swiftSources/CodexBar/HiddenWindowView.swiftSources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swiftSources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainAccessGate.swiftSources/CodexBarCore/KeychainAccessPreflight.swiftSources/CodexBarCore/KeychainNoUIQuery.swiftSources/CodexBarCore/KeychainCacheStore.swiftSources/CodexBarCore/CookieHeaderCache.swift