source dump of claude code
at main 112 lines 4.2 kB view raw
1/** 2 * Eligibility check for remote managed settings. 3 * 4 * The cache state itself lives in syncCacheState.ts (a leaf, no auth import). 5 * This file keeps isRemoteManagedSettingsEligible — the one function that 6 * needs auth.ts — plus resetSyncCache wrapped to clear the local eligibility 7 * mirror alongside the leaf's state. 8 */ 9 10import { CLAUDE_AI_INFERENCE_SCOPE } from '../../constants/oauth.js' 11import { 12 getAnthropicApiKeyWithSource, 13 getClaudeAIOAuthTokens, 14} from '../../utils/auth.js' 15import { 16 getAPIProvider, 17 isFirstPartyAnthropicBaseUrl, 18} from '../../utils/model/providers.js' 19 20import { 21 resetSyncCache as resetLeafCache, 22 setEligibility, 23} from './syncCacheState.js' 24 25let cached: boolean | undefined 26 27export function resetSyncCache(): void { 28 cached = undefined 29 resetLeafCache() 30} 31 32/** 33 * Check if the current user is eligible for remote managed settings 34 * 35 * Eligibility: 36 * - Console users (API key): All eligible (must have actual key, not just apiKeyHelper) 37 * - OAuth users with known subscriptionType: Only Enterprise/C4E and Team 38 * - OAuth users with subscriptionType === null (externally-injected tokens via 39 * CLAUDE_CODE_OAUTH_TOKEN / FD, or keychain tokens missing metadata): Eligible — 40 * the API returns empty settings for ineligible orgs, so the cost of a false 41 * positive is one round-trip 42 * 43 * This is a pre-check to determine if we should query the API. 44 * The API will return empty settings for users without managed settings. 45 * 46 * IMPORTANT: This function must NOT call getSettings() or any function that calls 47 * getSettings() to avoid circular dependencies during settings loading. 48 */ 49export function isRemoteManagedSettingsEligible(): boolean { 50 if (cached !== undefined) return cached 51 52 // 3p provider users should not hit the settings endpoint 53 if (getAPIProvider() !== 'firstParty') { 54 return (cached = setEligibility(false)) 55 } 56 57 // Custom base URL users should not hit the settings endpoint 58 if (!isFirstPartyAnthropicBaseUrl()) { 59 return (cached = setEligibility(false)) 60 } 61 62 // Cowork runs in a VM with its own permission model; server-managed settings 63 // (designed for CLI/CCD) don't apply there, and per-surface settings don't 64 // exist yet. MDM/file-based managed settings still apply via settings.ts — 65 // those require physical deployment and a different IT intent. 66 if (process.env.CLAUDE_CODE_ENTRYPOINT === 'local-agent') { 67 return (cached = setEligibility(false)) 68 } 69 70 // Check OAuth first: most Claude.ai users have no API key in the keychain. 71 // The API key check spawns `security find-generic-password` (~20-50ms) which 72 // returns null for OAuth-only users. Checking OAuth first short-circuits 73 // that subprocess for the common case. 74 const tokens = getClaudeAIOAuthTokens() 75 76 // Externally-injected tokens (CCD via CLAUDE_CODE_OAUTH_TOKEN, CCR via 77 // CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR, Agent SDK, CI) carry no 78 // subscriptionType metadata — getClaudeAIOAuthTokens() constructs them with 79 // subscriptionType: null. The token itself is valid; let the API decide. 80 // fetchRemoteManagedSettings handles 204/404 gracefully (returns {}), and 81 // settings.ts falls through to MDM/file when remote is empty, so ineligible 82 // orgs pay one round-trip and nothing else changes. 83 if (tokens?.accessToken && tokens.subscriptionType === null) { 84 return (cached = setEligibility(true)) 85 } 86 87 if ( 88 tokens?.accessToken && 89 tokens.scopes?.includes(CLAUDE_AI_INFERENCE_SCOPE) && 90 (tokens.subscriptionType === 'enterprise' || 91 tokens.subscriptionType === 'team') 92 ) { 93 return (cached = setEligibility(true)) 94 } 95 96 // Console users (API key) are eligible if we can get the actual key 97 // Skip apiKeyHelper to avoid circular dependency with getSettings() 98 // Wrap in try-catch because getAnthropicApiKeyWithSource throws in CI/test environments 99 // when no API key is available 100 try { 101 const { key: apiKey } = getAnthropicApiKeyWithSource({ 102 skipRetrievingKeyFromApiKeyHelper: true, 103 }) 104 if (apiKey) { 105 return (cached = setEligibility(true)) 106 } 107 } catch { 108 // No API key available (e.g., CI/test environment) 109 } 110 111 return (cached = setEligibility(false)) 112}