Skip to content
源码分析手册

Global Config vs. State:CLI 的持久化“大脑”与状态仓库

一句话讲清楚

在 Claude Code 中,“设置(Settings)”定义了用户的偏好(如主题、编辑器模式),而“全局配置(Global Config)”则是 CLI 的内部数据库和状态存储。

如果说 Settings 是用户可以随时通过 /config 修改的“说明书”,那么 Global Config 就是 CLI 自动维护的“账本”。它存储了跨会话的各种关键数据:用户身份标识、OAuth 令牌、项目访问历史、甚至是服务器下发的 A/B 测试实验状态。将两者分离的核心目的在于保证内部状态的原子性与安全性——用户不应该也无需手动编辑这些文件,任何误操作都可能导致登录失效或项目信任关系丢失。

实现机制

Global Config 的核心逻辑实现在 claude-code-opensource/src/utils/config.ts 中。在物理层面上,它通常对应磁盘上的 ~/.claude.json 文件(在特定的 OAuth 环境下会有后缀)。

数据结构:GlobalConfig 与 ProjectConfig

GlobalConfig 是一个庞大的 TypeScript 接口,它包含全局状态,并通过 projects 字段维护了一个以目录路径为键的 ProjectConfig 映射表。

  • 全局状态:包括匿名化的 userIDoauthAccount(存储授权令牌和组织信息)、numStartups(启动次数统计)以及 cachedGrowthBookFeatures(用于离线运行的实验参数缓存)。
  • 项目状态:每个项目(即每个代码库目录)都有独立的 ProjectConfig。它记录了该项目是否已通过信任对话框(hasTrustDialogAccepted)、该项目的工具使用频率、Token 消耗统计(lastCost)以及上次会话的 ID。

读写机制:原子性与并发保护

由于用户可能在多个终端窗口中同时运行 Claude Code,配置文件面临严重的并发竞争风险。为了防止文件损坏,系统实现了复杂的保护机制:

  1. 带锁写入saveConfigWithLock 使用 .lock 文件确保同一时间只有一个进程能修改配置。
  2. 状态丢失检测:源码中有一个非常重要的函数 wouldLoseAuthState()。在写入前,系统会重新读取磁盘文件,如果发现磁盘上的新内容丢失了当前内存中存在的 OAuth 令牌或完成引导的状态(这通常意味着文件正被并发改写或已被截断),系统会拒绝写入并抛出错误,从而保护用户的登录状态。
  3. 多级备份:在每次执行关键写入前,CLI 都会在 ~/.claude/backups/ 目录下创建一个带时间戳的备份文件,并默认保留最近的 5 个版本。

实时更新:Freshness Watcher

CLI 并不是只在启动时加载一次配置。通过 startGlobalConfigFreshnessWatcher(),系统利用 fs.watchFile 以 1 秒为间隔轮询文件状态。一旦检测到 mtime(修改时间)变化,CLI 会在后台自动重新读取并更新内存中的配置缓存。这种设计使得在窗口 A 中完成登录或切换组织后,窗口 B 能立即感知到状态变化。

限制与陷阱

第一,配置不是设置。虽然 GlobalConfig 包含了一些可以通过 /config 看到的字段(如 theme),但大部分字段是“隐身”的。直接手动编辑 ~/.claude.json 是极其危险的行为,尤其是 projects 路径映射,一旦路径格式不一致,将导致该项目的信任状态失效。

第二,信任的继承性。项目信任(hasTrustDialogAccepted)存储在 ProjectConfig 中,系统在校验时还会递归向上查找父目录。如果你对 /Users/work 授权了信任,那么其子目录 /Users/work/project-a 会自动继承该信任。

第三,环境隔离GlobalConfig 的文件名受环境变量影响。如果你设置了特定的 OAuth 环境,CLI 会创建不同的配置文件,这意味着你的生产环境令牌和测试环境状态是天然物理隔离的。

第四,缓存的局限性。虽然 cachedGrowthBookFeatures 提供了离线支持,但大部分依赖服务器的状态(如云端 MCP 连接)在离线时仍会表现为不可用。

继续探索

如果你对实验参数是如何下发并影响 CLI 行为的感兴趣,下一步应查看 Feature Flags & GrowthBook 的集成细节。

如果你关心用户登录与身份验证流程,可以分析 src/services/oauth/ 目录下相关服务的实现。

如果你想了解 CLI 如何追踪你的使用习惯并生成统计报表,应查看 Analytics Pipeline 相关章节。

源码锚点

  • claude-code-opensource/src/utils/config.ts: GlobalConfig 结构定义、带锁写入与状态保护的核心实现。
📄 src/utils/config.ts — `GlobalConfig` 结构定义、带锁写入与状态保护的核心实现。L183-212 of 1818
typescript
export type GlobalConfig = {
  /**
   * @deprecated Use settings.apiKeyHelper instead.
   */
  apiKeyHelper?: string
  projects?: Record<string, ProjectConfig>
  numStartups: number
  installMethod?: InstallMethod
  autoUpdates?: boolean
  // Flag to distinguish protection-based disabling from user preference
  autoUpdatesProtectedForNative?: boolean
  // Session count when Doctor was last shown
  doctorShownAtSession?: number
  userID?: string
  theme: ThemeSetting
  hasCompletedOnboarding?: boolean
  // Tracks the last version that reset onboarding, used with MIN_VERSION_REQUIRING_ONBOARDING_RESET
  lastOnboardingVersion?: string
  // Tracks the last version for which release notes were seen, used for managing release notes
  lastReleaseNotesSeen?: string
  // Timestamp when changelog was last fetched (content stored in ~/.claude/cache/changelog.md)
  changelogLastFetched?: number
  // @deprecated - Migrated to ~/.claude/cache/changelog.md. Keep for migration support.
  cachedChangelog?: string
  mcpServers?: Record<string, McpServerConfig>
  // claude.ai MCP connectors that have successfully connected at least once.
  // Used to gate "connector unavailable" / "needs auth" startup notifications:
  // a connector the user has actually used is worth flagging when it breaks,
  // but an org-configured connector that's been needs-auth since day one is
  // something the user has demonstrably ignored and shouldn't nag about.
  • claude-code-opensource/src/utils/env.ts: 确定全局配置文件物理路径的逻辑。
📄 src/utils/env.ts — 确定全局配置文件物理路径的逻辑。L11-26 of 348
typescript
type Platform = 'win32' | 'darwin' | 'linux'

// Config and data paths
export const getGlobalClaudeFile = memoize((): string => {
  // Legacy fallback for backwards compatibility
  if (
    getFsImplementation().existsSync(
      join(getClaudeConfigHomeDir(), '.config.json'),
    )
  ) {
    return join(getClaudeConfigHomeDir(), '.config.json')
  }

  const filename = `.claude${fileSuffixForOauthConfig()}.json`
  return join(process.env.CLAUDE_CONFIG_DIR || homedir(), filename)
})
  • claude-code-opensource/src/utils/envUtils.ts: 处理配置目录(getClaudeConfigHomeDir)的工具函数。
📄 src/utils/envUtils.ts — 处理配置目录(`getClaudeConfigHomeDir`)的工具函数。L7-12 of 184
typescript
export const getClaudeConfigHomeDir = memoize(
  (): string => {
    return (
      process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')
    ).normalize('NFC')
  },
  • claude-code-opensource/src/utils/lockfile.ts: 基于文件的锁实现逻辑。
📄 src/utils/lockfile.ts — 基于文件的锁实现逻辑。L14-24 of 44
typescript
type Lockfile = typeof import('proper-lockfile')

let _lockfile: Lockfile | undefined

function getLockfile(): Lockfile {
  if (!_lockfile) {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    _lockfile = require('proper-lockfile') as Lockfile
  }
  return _lockfile
}

基于 Claude Code v2.1.88 开源快照的深度分析