第七章:配置系统——灵活性的来源
“一个工具的边界,由它的配置系统决定。”
7.1 配置系统全景
Claude Code 的配置系统远比"读一个 JSON 文件"复杂。它是一个多层级、多来源、动态感知的配置框架,需要满足以下需求:
个人偏好、项目配置、企业策略三类用户需求并存
文件变更实时热重载,无需重启
企业 MDM(移动设备管理)策略 30 分钟轮询
Schema 校验 + 向后兼容迁移
插件可注入配置层
CLAUDE.md 记忆文件系统(与代码配置并列)
graph TB
subgraph 配置层["Settings 配置层(JSON)"]
S1[policySettings 企业管控策略]
S2[flagSettings CLI Flag 注入]
S3[localSettings .claude/settings.local.json]
S4[projectSettings .claude/settings.json]
S5[userSettings ~/.claude/settings.json]
S6[pluginSettings 插件注入基础层]
end
subgraph 记忆层["CLAUDE.md 记忆层(Markdown)"]
M1[管理员记忆 /etc/claude-code/CLAUDE.md]
M2[用户记忆 ~/.claude/CLAUDE.md]
M3[项目记忆 CLAUDE.md / .claude/rules/*.md]
M4[本地记忆 CLAUDE.local.md]
end
subgraph 全局配置["GlobalConfig(~/.claude/.claude.json)"]
G1[numStartups 启动次数]
G2[ProjectConfig 项目级缓存配置]
G3[allowedTools 允许的工具列表]
end
S1 -->|优先级最高| Merge
S2 --> Merge
S3 --> Merge
S4 --> Merge
S5 --> Merge
S6 -->|基础层| Merge
Merge --> AppState
M1 --> ContextPrompt
M2 --> ContextPrompt
M3 --> ContextPrompt
M4 -->|优先级最高| ContextPrompt
7.2 Settings 来源层级(SettingSource)
src/utils/settings/constants.ts 定义了配置来源的优先级顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 export const SETTING_SOURCES = [ 'pluginSettings' , 'userSettings' , 'projectSettings' , 'localSettings' , 'flagSettings' , 'policySettings' , ] as const export type SettingSource = (typeof SETTING_SOURCES)[number ]export type EditableSettingSource = 'userSettings' | 'projectSettings' | 'localSettings'
关键规则 :
来源
文件路径
可编辑
说明
pluginSettings
内存
否
插件通过 setPluginSettingsBase() 注入
userSettings
~/.claude/settings.json
是
用户全局,跨项目生效
projectSettings
.claude/settings.json
是
可提交 git,团队共享
localSettings
.claude/settings.local.json
是
本地覆盖,gitignore
flagSettings
内存
否
CLI --permission-mode 等 flag 注入
policySettings
/etc/claude-code/managed-settings.json(Linux)/Library/Application Support/ClaudeCode/(macOS)C:\Program Files\ClaudeCode\(Windows)
否
企业管控,只读
1 2 3 4 5 6 export function getEnabledSettingSources ( ): SettingSource [] { return SETTING_SOURCES.filter(s => isSettingSourceEnabled(s)) }
7.3 SettingsSchema:Zod 类型约束
所有配置字段都由 src/utils/settings/types.ts 中的 SettingsSchema 通过 Zod 定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 export const SettingsSchema = () => z.object({ model: z.string().optional(), availableModels: z.array(ModelOptionSchema).optional(), modelOverrides: z.record(z.string()).optional(), permissions: z.object({ allow: z.array(z.string()), deny: z.array(z.string()), ask: z.array(z.string()), defaultMode: PermissionModeSchema.optional(), additionalDirectories: z.array(z.string()).optional(), }).optional(), env: z.record(z.string()).optional(), hooks: z.record(z.array(HookEntrySchema)).optional(), mcpServers: z.record(McpServerConfigSchema).optional(), enabledMcpjsonServers: z.array(z.string()).optional(), disabledMcpjsonServers: z.array(z.string()).optional(), sandbox: SandboxConfigSchema.optional(), cleanupPeriodDays: z.number().min(1 ).optional(), theme: ThemeSettingSchema.optional(), effortLevel: z.number().optional(), autoMode: AutoModeRulesSchema.optional(), })
可定制面(CUSTOMIZATION_SURFACES) :用户可配置的功能入口列表:
1 export const CUSTOMIZATION_SURFACES = ['skills' , 'agents' , 'hooks' , 'mcp' ] as const
7.4 三级缓存架构
src/utils/settings/settingsCache.ts 实现了三层缓存,避免重复的磁盘 I/O 和 Zod 解析:
graph LR
App[应用读取设置] --> SC{sessionSettingsCache}
SC -->|命中| Return[返回合并结果]
SC -->|缺失| PSC{perSourceCache}
PSC -->|命中| Merge[合并各来源]
PSC -->|缺失| PFC{parseFileCache}
PFC -->|命中| UseCache[使用解析结果]
PFC -->|缺失| Disk[读取磁盘 + Zod 解析]
Disk --> PFC
UseCache --> PSC
Merge --> SC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let sessionSettingsCache: SettingsWithErrors | null = null const perSourceCache = new Map<SettingSource, SettingsJson | null >() const parseFileCache = new Map<string , ParsedSettings>() export function resetSettingsCache ( ): void { sessionSettingsCache = null perSourceCache.clear() parseFileCache.clear() } let pluginSettingsBase: Record<string , unknown> | undefined
设计亮点 :parseFileCache 的存在解决了启动时多次调用 parseSettingsFile 读取同一文件的重复问题(getSettingsForSource 和 loadSettingsFromDisk 都会调用)。
7.5 企业策略配置:Drop-in 目录
企业级别的 policySettings 支持通过**投放目录(drop-in directory)**合并多个配置片段:
1 2 3 4 5 6 /etc/claude-code/ ├── managed-settings.json # 基础配置(首先加载) └── managed-settings.d/ # Drop-in 目录 ├── 10-network-policy.json # 按字母顺序加载 ├── 20-tool-restrictions.json # 后加载的覆盖先加载的 └── 30-model-config.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export function loadManagedFileSettings ( ): SettingsJson | null { const base = parseSettingsFile(getManagedFilePath()) const dropInDir = getManagedSettingsDropInDir() const dropInFiles = readdirSync(dropInDir) .filter(f => f.endsWith('.json' )) .sort() let result = base ?? {} for (const file of dropInFiles) { const fragment = parseSettingsFile(join(dropInDir, file)) result = deepMerge(result, fragment) } return result }
平台路径 (managedPath.ts):
1 2 3 4 5 6 7 export const getManagedFilePath = memoize(function ( ): string { switch (getPlatform()) { case 'macos' : return '/Library/Application Support/ClaudeCode' case 'windows' : return 'C:\\Program Files\\ClaudeCode' default : return '/etc/claude-code' } })
7.6 文件变更检测(changeDetector.ts)
设置文件变化时需要热重载,changeDetector.ts 实现了完整的文件监听体系:
sequenceDiagram
participant FS as 文件系统
participant Chokidar as chokidar 监听
participant Stability as 稳定性检测器
participant Internal as internalWrites 内部写入标记
participant FanOut as fanOut 分发器
participant Listener as 设置变更监听器
FS->>Chokidar: 文件 change 事件
Chokidar->>Internal: consumeInternalWrite(path)?
Internal-->>Chokidar: false(外部变更)
Chokidar->>Stability: 启动稳定性计时器
Note over Stability: 等待 1000ms 无变化
Stability->>FanOut: 文件已稳定,触发分发
FanOut->>FanOut: resetSettingsCache()
FanOut->>Listener: 通知所有订阅者
关键常量 :
1 2 3 4 5 6 7 8 9 10 11 12 const FILE_STABILITY_THRESHOLD_MS = 1000 const FILE_STABILITY_POLL_INTERVAL_MS = 500 const INTERNAL_WRITE_WINDOW_MS = 5000 const MDM_POLL_INTERVAL_MS = 30 * 60 * 1000 const DELETION_GRACE_MS = 2000
fanOut 模式 (单生产者 + 多消费者):
1 2 3 4 5 6 7 function fanOut (source: SettingSource ): void { resetSettingsCache() for (const listener of listeners) { listener(source) } }
防重入设计 :internalWrites.ts 独立管理内部写入时间戳,避免 Claude Code 自身写入 settings 时触发无意义的热重载:
1 2 3 4 5 6 7 markInternalWrite(resolvedPath) if (consumeInternalWrite(path, INTERNAL_WRITE_WINDOW_MS)) { return }
7.7 Schema 校验与错误提示
validation.ts 将 Zod 错误转化为对人类友好的结构化错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export type ValidationError = { file?: string path: FieldPath message: string expected?: string invalidValue?: unknown suggestion?: string docLink?: string mcpErrorMetadata?: { scope: ConfigScope serverName?: string severity?: 'fatal' | 'warning' } }
容错设计 :filterInvalidPermissionRules 实现了单规则隔离 ——一条错误的权限规则不会导致整个文件解析失败:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export function filterInvalidPermissionRules ( data: unknown, filePath: string , ): ValidationError [] { for (const key of ['allow' , 'deny' , 'ask' ]) { perms[key] = rules.filter(rule => { const result = validatePermissionRule(rule) if (!result.valid) { warnings.push({ ... }) return false } return true }) } return warnings }
实时文件校验 :validateSettingsFileContent 在用户通过编辑工具修改 settings 时立即验证,失败时返回完整 JSON Schema 帮助用户修正:
1 2 3 4 5 6 7 8 9 10 11 12 export function validateSettingsFileContent (content: string ): | { isValid: true } | { isValid: false ; error: string ; fullSchema: string } { const result = SettingsSchema().strict().safeParse(jsonData) if (!result.success) { return { isValid: false , error: formatZodError(result.error, 'settings' ), fullSchema: generateSettingsJSONSchema(), } } }
7.8 设置变更应用(applySettingsChange)
当文件变更通知到达后,applySettingsChange.ts 负责将新配置原子性地应用到 AppState:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 export function applySettingsChange ( source: SettingSource, setAppState: (f: (prev: AppState) => AppState) => void , ): void { const newSettings = getInitialSettings() const updatedRules = loadAllPermissionRulesFromDisk() updateHooksConfigSnapshot() setAppState(prev => { let newContext = syncPermissionRulesFromDisk(prev.toolPermissionContext, updatedRules) if (process.env.USER_TYPE === 'ant' ) { const overlyBroad = findOverlyBroadBashPermissions(updatedRules, []) if (overlyBroad.length > 0 ) { newContext = removeDangerousPermissions(newContext, overlyBroad) } } const effortChanged = prev.settings.effortLevel !== newSettings.effortLevel return { ...prev, settings: newSettings, toolPermissionContext: newContext, ...(effortChanged && newSettings.effortLevel !== undefined ? { effortValue: newSettings.effortLevel } : {}), } }) }
除了 JSON 配置,Claude Code 还有一套基于 Markdown 文件 的"记忆"系统(src/utils/claudemd.ts),用于向 AI 注入持久化的上下文指令。
9.1 四层记忆文件
1 2 3 4 5 6 7 8 加载顺序(越靠后优先级越高): ① 管理员记忆 /etc/claude-code/CLAUDE.md 全局用户指令 ② 用户记忆 ~/.claude/CLAUDE.md 跨项目个人指令 ③ 项目记忆 CLAUDE.md 可提交 git 的项目约定 .claude/CLAUDE.md .claude/rules/*.md ④ 本地记忆 CLAUDE.local.md 不提交 git 的本地覆盖
文件发现机制 :从当前工作目录向上遍历至 git 根目录,每个层级都检查 CLAUDE.md、.claude/CLAUDE.md 和 .claude/rules/ 下的所有 .md 文件。
9.2 @include 指令
记忆文件支持通过 @ 语法引用其他文件,实现模块化拆分:
1 2 3 4 5 6 @./api-conventions.md @./testing-guidelines.md ## 通用规则 - 使用 Go 驼峰命名法
语法规则 :
语法
解析方式
@path
相对路径(同 @./path)
@./relative
相对当前文件目录
@~/home/path
用户主目录
@/absolute/path
绝对路径
安全约束 :
仅在叶子文本节点中生效(不在代码块中处理)
循环引用检测(追踪已处理文件集合)
不存在的文件静默忽略
只允许文本类型文件(白名单:.md、.ts、.go、.py 等 50+ 扩展名)
9.3 Frontmatter 路径过滤
记忆文件可以通过 Frontmatter 指定只对特定路径的文件生效:
1 2 3 4 5 6 7 8 --- paths: - src/api/** - src/services/**--- ## API 编写规范 - 所有接口必须有错误处理
仅当用户正在操作匹配路径的文件时,该记忆文件才会被注入上下文。
9.4 HTML 注释剥离
stripHtmlComments() 使用 marked 词法器识别块级 HTML 注释,将其从注入内容中剥离:
1 2 3 4 5 6 export function stripHtmlComments (content: string ): { content: string ; stripped: boolean } { if (!content.includes('<!--' )) return { content, stripped: false } return stripHtmlCommentsFromTokens(new Lexer({ gfm: false }).lex(content)) }
9.5 自动记忆(Auto Memory)
Claude Code 维护一个自动写入的记忆文件(MEMORY.md):
1 2 3 4 export function getAutoMemEntrypoint ( ): string { return join(getClaudeConfigHomeDir(), 'projects' , normalizedProjectPath, 'memory' , 'MEMORY.md' ) }
自动记忆的内容在加载时会进行截断处理,防止 token 超限:
1 2 export const MAX_MEMORY_CHARACTER_COUNT = 40000
7.10 GlobalConfig:会话级别配置
除了 settings.json,Claude Code 还有一个 ~/.claude/.claude.json(GlobalConfig)用于存储无需 Schema 验证的松散全局状态 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export type GlobalConfig = { numStartups: number lastSplashScreenVersion: string installMethod?: string projects: Record<string , ProjectConfig> } export type ProjectConfig = { allowedTools: string [] mcpContextUris: string [] mcpServers?: Record<string , McpServerConfig> lastAPIDuration?: number }
特点 :
通过 lockfile 实现写入互斥,防止并发写入损坏
读取使用 memoize 缓存,避免频繁磁盘 I/O
用于保存跨会话的"软"偏好(不属于严格的权限/模型配置)
7.11 配置迁移系统
src/migrations/ 目录下维护了一套有序的迁移脚本,用于在版本升级时自动迁移用户配置:
1 2 3 4 5 6 7 src/migrations/ ├── migrateSonnet45ToSonnet46.ts # Sonnet 4.5 → 4.6 别名迁移 ├── migrateOpusToOpus1m.ts # Opus → Opus 1m 迁移 ├── migrateGitignore.ts # .gitignore 配置迁移 ├── migrateAutoUpdater.ts # 自动更新器设置迁移 ├── migrateTheme.ts # 主题设置迁移 └── ...(共 11 个迁移脚本)
迁移特点 :
以 migrateSonnet45ToSonnet46 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 export function migrateSonnet45ToSonnet46 ( ): void { if (getAPIProvider() !== 'firstParty' ) return if (!isProSubscriber() && ...) return const model = getSettingsForSource('userSettings' )?.model if (!['claude-sonnet-4-5-20250929' , ...].includes(model)) return updateSettingsForSource('userSettings' , { model: has1m ? 'sonnet[1m]' : 'sonnet' , }) if (config.numStartups > 1 ) { saveGlobalConfig(current => ({ ...current, sonnet45To46MigrationTimestamp: Date .now(), })) } }
迁移设计原则 :
幂等性 :多次执行结果相同
精确匹配 :只处理明确需要迁移的旧值,不影响用户自定义配置
来源隔离 :只读写 userSettings,不触碰 projectSettings(项目配置由项目维护者管理)
向前跳过 :不满足迁移条件时直接 return,不报错
7.12 配置读写完整流程
sequenceDiagram
participant User as 用户操作
participant App as 应用启动/运行
participant Cache as 三级缓存
participant Disk as 磁盘
participant Zod as Zod 校验
participant Chokidar as 文件监听
participant Migration as 迁移脚本
App->>Migration: 启动时运行迁移脚本
Migration->>Disk: 按需更新 userSettings
App->>Cache: getInitialSettings()
Cache->>Cache: 命中 sessionSettingsCache?
Cache->>Disk: 读取各层 JSON 文件
Disk->>Zod: filterInvalidPermissionRules
Zod->>Zod: SettingsSchema().parse()
Zod->>Cache: 写入 parseFileCache + perSourceCache
Cache->>App: 合并后的 SettingsJson
User->>Disk: 编辑 .claude/settings.json
Disk->>Chokidar: change 事件
Chokidar->>Chokidar: 等待 1000ms 稳定
Chokidar->>Cache: resetSettingsCache()
Chokidar->>App: 通知 applySettingsChange()
App->>Cache: 重新读取(缓存已清)
7.13 设计原则总结
#
原则
具体体现
1
优先级清晰
6 个来源按固定顺序合并,后者覆盖前者
2
三级缓存
session/perSource/parseFile 三层,单一 resetSettingsCache() 失效
3
容错解析
坏规则被过滤,不影响其他合法规则
4
热重载
chokidar 监听 + 稳定性阈值,实时感知文件变化
5
MDM 轮询
企业策略 30 分钟轮询,无需用户手动刷新
6
Drop-in 扩展
managed-settings.d/ 支持按字母顺序多片段合并
7
内部写入抑制
internalWrites.ts 避免自写自触发的循环
8
幂等迁移
版本升级迁移脚本精确匹配 + 幂等写入
9
记忆优先级
CLAUDE.md 文件越靠近当前目录,优先级越高
10
@include 模块化
记忆文件可引用其他文件,支持团队知识拆分
7.14 核心文件速查表
文件
职责
src/utils/settings/constants.ts
SETTING_SOURCES 优先级顺序,SettingSource 类型
src/utils/settings/types.ts
SettingsSchema(Zod):所有配置字段定义
src/utils/settings/settings.ts
文件读写:getInitialSettings、loadManagedFileSettings、updateSettingsForSource
src/utils/settings/settingsCache.ts
三级缓存:session / perSource / parseFile
src/utils/settings/changeDetector.ts
chokidar 监听 + 稳定性检测 + MDM 轮询 + fanOut
src/utils/settings/internalWrites.ts
内部写入时间戳标记,防止自写自触发
src/utils/settings/validation.ts
Zod 错误格式化,filterInvalidPermissionRules,validateSettingsFileContent
src/utils/settings/managedPath.ts
企业策略文件路径(按平台返回)
src/utils/settings/applySettingsChange.ts
配置变更后原子更新 AppState + 权限同步
src/utils/claudemd.ts
CLAUDE.md 记忆文件发现、加载、@include 解析
src/utils/config.ts
GlobalConfig(~/.claude/.claude.json)读写
src/migrations/
11 个版本迁移脚本