| /** | |
| * Leaf state module for the remote-managed-settings sync cache. | |
| * | |
| * Split from syncCache.ts to break the settings.ts β syncCache.ts β auth.ts β | |
| * settings.ts cycle. auth.ts sits inside the large settings SCC; importing it | |
| * from settings.ts's own dependency chain pulls hundreds of modules into the | |
| * eagerly-evaluated SCC at startup. | |
| * | |
| * This module imports only leaves (path, envUtils, file, json, types, | |
| * settings/settingsCache β also a leaf, only type-imports validation). settings.ts | |
| * reads the cache from here. syncCache.ts keeps isRemoteManagedSettingsEligible | |
| * (the auth-touching part) and re-exports everything from here for callers that | |
| * don't care about the cycle. | |
| * | |
| * Eligibility is a tri-state here: undefined (not yet determined β return | |
| * null), false (ineligible β return null), true (proceed). managedEnv.ts | |
| * calls isRemoteManagedSettingsEligible() just before the policySettings | |
| * read β after userSettings/flagSettings env vars are applied, so the check | |
| * sees config-provided CLAUDE_CODE_USE_BEDROCK/ANTHROPIC_BASE_URL. That call | |
| * computes once and mirrors the result here via setEligibility(). Every | |
| * subsequent read hits the cached bool instead of re-running the auth chain. | |
| */ | |
| import { join } from 'path' | |
| import { getClaudeConfigHomeDir } from '../../utils/envUtils.js' | |
| import { readFileSync } from '../../utils/fileRead.js' | |
| import { stripBOM } from '../../utils/jsonRead.js' | |
| import { resetSettingsCache } from '../../utils/settings/settingsCache.js' | |
| import type { SettingsJson } from '../../utils/settings/types.js' | |
| import { jsonParse } from '../../utils/slowOperations.js' | |
| const SETTINGS_FILENAME = 'remote-settings.json' | |
| let sessionCache: SettingsJson | null = null | |
| let eligible: boolean | undefined | |
| export function setSessionCache(value: SettingsJson | null): void { | |
| sessionCache = value | |
| } | |
| export function resetSyncCache(): void { | |
| sessionCache = null | |
| eligible = undefined | |
| } | |
| export function setEligibility(v: boolean): boolean { | |
| eligible = v | |
| return v | |
| } | |
| export function getSettingsPath(): string { | |
| return join(getClaudeConfigHomeDir(), SETTINGS_FILENAME) | |
| } | |
| // sync IO β settings pipeline is sync. fileRead and jsonRead are leaves; | |
| // file.ts and json.ts both sit in the settings SCC. | |
| function loadSettings(): SettingsJson | null { | |
| try { | |
| const content = readFileSync(getSettingsPath()) | |
| const data: unknown = jsonParse(stripBOM(content)) | |
| if (!data || typeof data !== 'object' || Array.isArray(data)) { | |
| return null | |
| } | |
| return data as SettingsJson | |
| } catch { | |
| return null | |
| } | |
| } | |
| export function getRemoteManagedSettingsSyncFromCache(): SettingsJson | null { | |
| if (eligible !== true) return null | |
| if (sessionCache) return sessionCache | |
| const cachedSettings = loadSettings() | |
| if (cachedSettings) { | |
| sessionCache = cachedSettings | |
| // Remote settings just became available for the first time. Any merged | |
| // getSettings_DEPRECATED() result cached before this moment is missing | |
| // the policySettings layer (the `eligible !== true` guard above returned | |
| // null). Flush so the next merged read re-merges with this layer visible. | |
| // | |
| // Fires at most once: subsequent calls hit `if (sessionCache)` above. | |
| // When called from loadSettingsFromDisk() (settings.ts:546), the merged | |
| // cache is still null (setSessionSettingsCache runs at :732 after | |
| // loadSettingsFromDisk returns) β no-op. The async-fetch arm (index.ts | |
| // setSessionCache + notifyChange) already handles its own reset. | |
| // | |
| // gh-23085: isBridgeEnabled() at main.tsx Commander-definition time | |
| // (before preAction β init() β isRemoteManagedSettingsEligible()) reached | |
| // getSettings_DEPRECATED() at auth.ts:115. The try/catch in bridgeEnabled | |
| // swallowed the later getGlobalConfig() throw, but the merged settings | |
| // cache was already poisoned. See managedSettingsHeadless.int.test.ts. | |
| resetSettingsCache() | |
| return cachedSettings | |
| } | |
| return null | |
| } | |