source dump of claude code
at main 116 lines 4.8 kB view raw
1/** 2 * Minimal module for firing macOS keychain reads in parallel with main.tsx 3 * module evaluation, same pattern as startMdmRawRead() in settings/mdm/rawRead.ts. 4 * 5 * isRemoteManagedSettingsEligible() reads two separate keychain entries 6 * SEQUENTIALLY via sync execSync during applySafeConfigEnvironmentVariables(): 7 * 1. "Claude Code-credentials" (OAuth tokens) — ~32ms 8 * 2. "Claude Code" (legacy API key) — ~33ms 9 * Sequential cost: ~65ms on every macOS startup. 10 * 11 * Firing both here lets the subprocesses run in parallel with the ~65ms of 12 * main.tsx imports. ensureKeychainPrefetchCompleted() is awaited alongside 13 * ensureMdmSettingsLoaded() in main.tsx preAction — nearly free since the 14 * subprocesses finish during import evaluation. Sync read() and 15 * getApiKeyFromConfigOrMacOSKeychain() then hit their caches. 16 * 17 * Imports stay minimal: child_process + macOsKeychainHelpers.ts (NOT 18 * macOsKeychainStorage.ts — that pulls in execa → human-signals → 19 * cross-spawn, ~58ms of synchronous module init). The helpers file's own 20 * import chain (envUtils, oauth constants, crypto) is already evaluated by 21 * startupProfiler.ts at main.tsx:5, so no new module-init cost lands here. 22 */ 23 24import { execFile } from 'child_process' 25import { isBareMode } from '../envUtils.js' 26import { 27 CREDENTIALS_SERVICE_SUFFIX, 28 getMacOsKeychainStorageServiceName, 29 getUsername, 30 primeKeychainCacheFromPrefetch, 31} from './macOsKeychainHelpers.js' 32 33const KEYCHAIN_PREFETCH_TIMEOUT_MS = 10_000 34 35// Shared with auth.ts getApiKeyFromConfigOrMacOSKeychain() so it can skip its 36// sync spawn when the prefetch already landed. Distinguishing "not started" (null) 37// from "completed with no key" ({ stdout: null }) lets the sync reader only 38// trust a completed prefetch. 39let legacyApiKeyPrefetch: { stdout: string | null } | null = null 40 41let prefetchPromise: Promise<void> | null = null 42 43type SpawnResult = { stdout: string | null; timedOut: boolean } 44 45function spawnSecurity(serviceName: string): Promise<SpawnResult> { 46 return new Promise(resolve => { 47 execFile( 48 'security', 49 ['find-generic-password', '-a', getUsername(), '-w', '-s', serviceName], 50 { encoding: 'utf-8', timeout: KEYCHAIN_PREFETCH_TIMEOUT_MS }, 51 (err, stdout) => { 52 // Exit 44 (entry not found) is a valid "no key" result and safe to 53 // prime as null. But timeout (err.killed) means the keychain MAY have 54 // a key we couldn't fetch — don't prime, let sync spawn retry. 55 // biome-ignore lint/nursery/noFloatingPromises: resolve() is not a floating promise 56 resolve({ 57 stdout: err ? null : stdout?.trim() || null, 58 timedOut: Boolean(err && 'killed' in err && err.killed), 59 }) 60 }, 61 ) 62 }) 63} 64 65/** 66 * Fire both keychain reads in parallel. Called at main.tsx top-level 67 * immediately after startMdmRawRead(). Non-darwin is a no-op. 68 */ 69export function startKeychainPrefetch(): void { 70 if (process.platform !== 'darwin' || prefetchPromise || isBareMode()) return 71 72 // Fire both subprocesses immediately (non-blocking). They run in parallel 73 // with each other AND with main.tsx imports. The await in Promise.all 74 // happens later via ensureKeychainPrefetchCompleted(). 75 const oauthSpawn = spawnSecurity( 76 getMacOsKeychainStorageServiceName(CREDENTIALS_SERVICE_SUFFIX), 77 ) 78 const legacySpawn = spawnSecurity(getMacOsKeychainStorageServiceName()) 79 80 prefetchPromise = Promise.all([oauthSpawn, legacySpawn]).then( 81 ([oauth, legacy]) => { 82 // Timed-out prefetch: don't prime. Sync read/spawn will retry with its 83 // own (longer) timeout. Priming null here would shadow a key that the 84 // sync path might successfully fetch. 85 if (!oauth.timedOut) primeKeychainCacheFromPrefetch(oauth.stdout) 86 if (!legacy.timedOut) legacyApiKeyPrefetch = { stdout: legacy.stdout } 87 }, 88 ) 89} 90 91/** 92 * Await prefetch completion. Called in main.tsx preAction alongside 93 * ensureMdmSettingsLoaded() — nearly free since subprocesses finish during 94 * the ~65ms of main.tsx imports. Resolves immediately on non-darwin. 95 */ 96export async function ensureKeychainPrefetchCompleted(): Promise<void> { 97 if (prefetchPromise) await prefetchPromise 98} 99 100/** 101 * Consumed by getApiKeyFromConfigOrMacOSKeychain() in auth.ts before it 102 * falls through to sync execSync. Returns null if prefetch hasn't completed. 103 */ 104export function getLegacyApiKeyPrefetchResult(): { 105 stdout: string | null 106} | null { 107 return legacyApiKeyPrefetch 108} 109 110/** 111 * Clear prefetch result. Called alongside getApiKeyFromConfigOrMacOSKeychain 112 * cache invalidation so a stale prefetch doesn't shadow a fresh write. 113 */ 114export function clearLegacyApiKeyPrefetch(): void { 115 legacyApiKeyPrefetch = null 116}