6.7 KiB
summary, read_when
| summary | read_when | |||
|---|---|---|---|---|
| Current keychain behavior: legacy migration, Claude OAuth keychain bootstrap, and prompt mitigation. |
|
Keychain Fix: Current State
Scope change from the original doc
The original fix (migrating legacy CodexBar keychain items to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) is
still in place, but the architecture has changed:
- Provider settings and manual secrets are now persisted in
~/.codexbar/config.json. - Legacy keychain stores are still present mainly to migrate old installs, then clear old items.
- Keychain is still used for runtime cache entries (for example
com.steipete.codexbar.cache) and Claude OAuth bootstrap reads from Claude CLI keychain (Claude Code-credentials).
Then vs now
| 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). |
Current keychain surfaces for Claude
1. Legacy CodexBar keychain migration (V1)
Sources/CodexBar/KeychainMigration.swift migrates legacy com.steipete.CodexBar items (for example
claude-cookie) to AfterFirstUnlockThisDeviceOnly.
- Gate key:
KeychainMigrationV1Completed - Runs once unless flag is reset.
- Covers legacy CodexBar-managed accounts only (not Claude CLI's own keychain service).
2. Claude OAuth bootstrap path
Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift
Load order for credentials:
- Environment override (
CODEXBAR_CLAUDE_OAUTH_TOKEN, scopes env key). - In-memory cache.
- CodexBar keychain cache (
com.steipete.codexbar.cache, accountoauth.claude). ~/.claude/.credentials.json.- Claude CLI keychain service:
Claude Code-credentials(promptable fallback).
Prompt mitigation:
- Non-interactive keychain probes use
KeychainNoUIQuerywithLAContext.interactionNotAllowed. - Pre-alert is shown only when preflight suggests interaction may be required.
- Denials are cooled down in the background via
claudeOAuthKeychainDeniedUntil(ClaudeOAuthKeychainAccessGate). User actions (menu open / manual refresh) clear this cooldown. - Auto-mode availability checks use non-interactive loads with prompt cooldown respected.
- Background cache-sync-on-change also performs non-interactive Claude keychain probes (
syncWithClaudeKeychainIfChanged) and can update cached OAuth data when the token changes.
Why two Claude keychain prompts can still happen on startup
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:
- Interactive read of the newest discovered keychain candidate.
- If that does not return usable data, interactive legacy service-level fallback read.
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:
- one path is a direct secret-data read for the key item,
- the fallback path is a key/service access query.
This is OS/keychain ACL behavior, not a ThisDeviceOnly migration issue.
3. Claude web cookie cache
Sources/CodexBarCore/CookieHeaderCache.swift and Sources/CodexBarCore/KeychainCacheStore.swift
- Browser-imported Claude session cookies are cached in keychain service
com.steipete.codexbar.cache. - Account key is
cookie.claude. - Cache writes use
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. - Users can clear browser-cookie cache entries from Preferences → Debug → Caches or with
codexbar cache clear --cookies.--provider <id>scopes cookie clearing to one provider and includes scoped Codex managed-account cookie keys.
What still uses ThisDeviceOnly
- Legacy store implementations (
CookieHeaderStore, token stores, MiniMax stores) still write usingkSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. - Keychain cache store (
com.steipete.codexbar.cache) also writes withThisDeviceOnly.
Disable keychain access behavior
Advanced -> Disable Keychain access sets debugDisableKeychainAccess and flips KeychainAccessGate.isDisabled.
Effects:
- Blocks keychain reads/writes in legacy stores.
- Disables keychain-backed cookie auto-import paths.
- Forces cookie source resolution to manual/off where applicable.
Verification
Check legacy migration flag
defaults read com.steipete.codexbar KeychainMigrationV1Completed
Check Claude OAuth keychain cooldown
defaults read com.steipete.codexbar claudeOAuthKeychainDeniedUntil
Inspect keychain-related logs
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
Reset migration for local testing
defaults delete com.steipete.codexbar KeychainMigrationV1Completed
./Scripts/compile_and_run.sh
Key files (current)
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