第七章:配置系统——灵活性的来源

“一个工具的边界,由它的配置系统决定。”


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', // ~/.claude/settings.json
'projectSettings', // .claude/settings.json
'localSettings', // .claude/settings.local.json(不提交 git)
'flagSettings', // CLI flag / IDE 注入
'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[] {
// policySettings 和 flagSettings 始终启用
// 其余来源可通过企业策略禁用
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 系统
hooks: z.record(z.array(HookEntrySchema)).optional(),

// MCP 服务器
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 读取同一文件的重复问题(getSettingsForSourceloadSettingsFromDisk 都会调用)。


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
// settings.ts:加载企业托管设置
export function loadManagedFileSettings(): SettingsJson | null {
// 1. 加载基础配置
const base = parseSettingsFile(getManagedFilePath())

// 2. 读取 drop-in 目录(按字母排序)
const dropInDir = getManagedSettingsDropInDir()
const dropInFiles = readdirSync(dropInDir)
.filter(f => f.endsWith('.json'))
.sort() // 字母序,数字前缀控制优先级

// 3. 逐个深度合并(后者覆盖前者)
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
// 文件写入后等待 1s 无变化才触发热重载
const FILE_STABILITY_THRESHOLD_MS = 1000
const FILE_STABILITY_POLL_INTERVAL_MS = 500

// 内部写入抑制窗口(5s 内由 Claude Code 自身写入的变更不触发重载)
const INTERNAL_WRITE_WINDOW_MS = 5000

// 企业 MDM 策略轮询间隔(30 分钟)
const MDM_POLL_INTERVAL_MS = 30 * 60 * 1000

// 文件删除后的宽限期(避免原子替换时误报)
const DELETION_GRACE_MS = 2000

fanOut 模式(单生产者 + 多消费者):

1
2
3
4
5
6
7
// 重置缓存只由 fanOut 触发一次,所有监听器共享同一次缓存清除
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
// 写入前标记(settings.ts 调用)
markInternalWrite(resolvedPath)

// chokidar 回调中检查(消费即删除,一次性)
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 // 点分字段路径(如 "permissions.allow")
message: string // 可读的错误描述
expected?: string // 期望的值或类型
invalidValue?: unknown // 实际的非法值
suggestion?: string // 修复建议(来自 validationTips.ts)
docLink?: string // 相关文档链接
mcpErrorMetadata?: { // MCP 专属元数据
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[] {
// 逐条验证 allow/deny/ask 数组中的每条规则
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(), // 完整 JSON Schema
}
}
}

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 {
// 1. 重读合并设置(缓存已由 fanOut 清除)
const newSettings = getInitialSettings()

// 2. 重载权限规则
const updatedRules = loadAllPermissionRulesFromDisk()

// 3. 刷新 Hooks 配置快照
updateHooksConfigSnapshot()

// 4. 原子更新 AppState
setAppState(prev => {
let newContext = syncPermissionRulesFromDisk(prev.toolPermissionContext, updatedRules)

// Ant 内部环境:清除过于宽泛的 Bash allow 规则
if (process.env.USER_TYPE === 'ant') {
const overlyBroad = findOverlyBroadBashPermissions(updatedRules, [])
if (overlyBroad.length > 0) {
newContext = removeDangerousPermissions(newContext, overlyBroad)
}
}

// 同步 effortLevel(仅当 settings 中的值真正变化时)
const effortChanged = prev.settings.effortLevel !== newSettings.effortLevel
return {
...prev,
settings: newSettings,
toolPermissionContext: newContext,
...(effortChanged && newSettings.effortLevel !== undefined
? { effortValue: newSettings.effortLevel }
: {}),
}
})
}

7.9 CLAUDE.md 记忆系统

除了 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
<!-- CLAUDE.md -->
@./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 }
// 通过 marked Lexer 标识 html 类型 token 再过滤
return stripHtmlCommentsFromTokens(new Lexer({ gfm: false }).lex(content))
}

9.5 自动记忆(Auto Memory)

Claude Code 维护一个自动写入的记忆文件(MEMORY.md):

1
2
3
4
// memdir/paths.ts
export function getAutoMemEntrypoint(): string {
return join(getClaudeConfigHomeDir(), 'projects', normalizedProjectPath, 'memory', 'MEMORY.md')
}

自动记忆的内容在加载时会进行截断处理,防止 token 超限:

1
2
// 超出 MAX_MEMORY_CHARACTER_COUNT(40000 字符)时截断
export const MAX_MEMORY_CHARACTER_COUNT = 40000

7.10 GlobalConfig:会话级别配置

除了 settings.json,Claude Code 还有一个 ~/.claude/.claude.jsonGlobalConfig)用于存储无需 Schema 验证的松散全局状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// config.ts
export type GlobalConfig = {
numStartups: number // 累计启动次数(用于迁移判断)
lastSplashScreenVersion: string
installMethod?: string
// 项目级别配置(以 git 根目录规范化路径为 key)
projects: Record<string, ProjectConfig>
}

export type ProjectConfig = {
allowedTools: string[] // 允许的工具列表
mcpContextUris: string[] // MCP 上下文 URI
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 {
// 1. 前置检查:仅对 firstParty API 用户迁移
if (getAPIProvider() !== 'firstParty') return

// 2. 用户资格检查:仅 Pro/Max/Team Premium 用户
if (!isProSubscriber() && ...) return

// 3. 精确匹配:只迁移明确指定 Sonnet 4.5 版本字符串的配置
const model = getSettingsForSource('userSettings')?.model
if (!['claude-sonnet-4-5-20250929', ...].includes(model)) return

// 4. 幂等写入:更新为别名('sonnet' 或 'sonnet[1m]')
updateSettingsForSource('userSettings', {
model: has1m ? 'sonnet[1m]' : 'sonnet',
})

// 5. 新用户跳过通知(numStartups > 1 才提示)
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 文件读写:getInitialSettingsloadManagedFileSettingsupdateSettingsForSource
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 错误格式化,filterInvalidPermissionRulesvalidateSettingsFileContent
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 个版本迁移脚本