设置与配置
5 层级联、SettingsSchema、更改检测、MDM 和远程管理设置
1.概述:为什么要级联?
Claude Code 在完全不同的环境中运行:独立开发人员的笔记本电脑、共享团队存储库、CI 代理以及由 IT 部门管理的企业队列。 单个平面配置文件无法同时满足所有这些需求。
解决方案是一个 优先级联:五个可独立写入的源按优先级从低到高的顺序合并。 每个层都可以添加或覆盖其下面的任何内容。 结果是单个合并 SettingsJson object 代码库的其余部分读取。
userSettings, projectSettings, 和 localSettings 可通过写入 updateSettingsForSource().
2. 5层级联
真理的规范来源是 constants.ts,它定义了 SETTING_SOURCES 数组按合并顺序:
export const SETTING_SOURCES = [ 'userSettings', // lowest priority 'projectSettings', 'localSettings', 'flagSettings', 'policySettings', // highest priority ] as const
后面的条目覆盖前面的条目。 合并发生在 loadSettingsFromDisk() via lodash mergeWith 使用自定义阵列重复数据删除策略。
全球开发者偏好。 用户可写。 在所有项目中共享。
致力于回购。 与整个团队共享。 版本控制。
自动忽略。 每个项目的个人覆盖不会在回购中发生。
CLI 标志。 合并文件 and 任何内联 SDK 设置。 未进行文件监视。
实施 IT/MDM。 第一个来源在四个子来源中胜出。
优先级堆栈 — 从低到高
级联流程图
来源:loadSettingsFromDisk() — 合并引擎
// settings.ts — simplified for clarity function loadSettingsFromDisk(): SettingsWithErrors { let mergedSettings: SettingsJson = {} // Plugin settings are the lowest-priority base layer const pluginSettings = getPluginSettingsBase() if (pluginSettings) { mergedSettings = mergeWith(mergedSettings, pluginSettings, settingsMergeCustomizer) } for (const source of getEnabledSettingSources()) { if (source === 'policySettings') { // First-source-wins among: remote → MDM → file → HKCU const policySettings = resolvePolicySettings() if (policySettings) { mergedSettings = mergeWith(mergedSettings, policySettings, settingsMergeCustomizer) } continue } const { settings } = parseSettingsFile(getSettingsFilePathForSource(source)) if (settings) { mergedSettings = mergeWith(mergedSettings, settings, settingsMergeCustomizer) } } return { settings: mergedSettings, errors: allErrors } }
3.SettingsSchema:可以配置什么
SettingsSchema in types.ts 是一个 Zod v4 架构,定义了每个有效的密钥。 它用于验证 every 合并前设置源。 无效文件表面 ValidationError[] 但不要使 Claude Code 崩溃 — 其余的有效配置仍会加载。
顶级键(选定)
| Key | Type | Purpose |
|---|---|---|
permissions | object | 允许/拒绝/询问数组、defaultMode、disableBypassPermissionsMode |
hooks | object | PreToolUse、PostToolUse、通知、SessionStart、Stop 等 |
env | 记录<字符串,字符串> | 注入每个会话的环境变量 |
model | string | 覆盖默认的克劳德模型 |
availableModels | string[] | 可选型号的企业白名单 |
allowedMcpServers | object[] | 企业 MCP 服务器白名单(按名称、命令或 URL) |
deniedMcpServers | object[] | 企业 MCP 服务器拒绝列表(拒绝列表击败允许列表) |
apiKeyHelper | string | 发出身份验证值的脚本路径 |
cleanupPeriodDays | number | 转录本保留天数(0 = 禁用持久性) |
strictPluginOnlyCustomization | 布尔 | 细绳[] | 将技能/代理/挂钩/mcp 锁定为仅插件交付 |
allowManagedHooksOnly | boolean | 在策略中设置时,仅运行托管挂钩 |
allowManagedPermissionRulesOnly | boolean | 在策略中设置时,仅适用托管权限规则 |
attribution | object | 自定义提交/PR 归属文本 |
sandbox | object | 沙箱配置(启用、网络、文件系统……) |
worktree | object | --worktree 标志的 symlinkDirectories、sparsePaths |
向后兼容合约
代码库注释声明了严格的规则,以避免破坏用户现有的配置文件:
- Allowed: 添加新的可选字段、新的枚举值,使验证更加宽松
- Forbidden: 删除字段、删除枚举值、将可选字段设为必填、重命名键
- The
.passthrough()权限对象上保留文件中的未知字段 filterInvalidPermissionRules()废除个别不良规则 before Zod 验证,因此一条错误规则不会使整个设置文件无效
设置文件示例
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"model": "claude-opus-4-5",
"env": {
"NODE_ENV": "development",
"LOG_LEVEL": "debug"
},
"permissions": {
"defaultMode": "acceptEdits",
"allow": ["Bash(git *)", "Read(**)"],
"deny": ["Bash(rm -rf *)"]
},
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "echo pre-bash" }]
}]
},
"cleanupPeriodDays": 7
}
4. 合并语义
Claude Code 使用 lodash mergeWith 在整个设置管道中,使用自定义 settingsMergeCustomizer:
// settings.ts export function settingsMergeCustomizer( objValue: unknown, srcValue: unknown, ): unknown { if (Array.isArray(objValue) && Array.isArray(srcValue)) { return uniq([...objValue, ...srcValue]) // deduplicated concatenation } return undefined // let lodash handle objects / scalars }
规则一览
- Objects ——深度融合。 较高层中的密钥会覆盖较低层中的相同密钥。
- Arrays - 连接和重复数据删除。 所有启用的来源的权限规则都会累积。
- Scalars — 较高层获胜(简单覆盖)。
- 删除通过
updateSettingsForSource()- 经过undefined作为一个值。 定制器检测到srcValue === undefined并打电话delete object[key].
updateSettingsForSource(),数组是 replaced (不合并)——调用者负责在传入之前计算所需的最终数组状态。
5.三层缓存
Claude Code 在关键启动路径上同步读取设置文件。 三个缓存层可防止冗余磁盘 I/O 和 Zod 重新解析:
| Cache | 键入者 | Holds | 无效于 |
|---|---|---|---|
sessionSettingsCache |
(singleton) | 完全合并 SettingsWithErrors |
resetSettingsCache() |
perSourceCache |
SettingSource |
Per-source SettingsJson | null |
resetSettingsCache() |
parseFileCache |
文件路径字符串 | Parsed { settings, errors } |
resetSettingsCache() |
所有三个缓存均由原子方式清除 resetSettingsCache()。 这一单一调用是变更检测器中的“扇出”步骤:一次缓存重置意味着一次磁盘重新加载,无论订阅了多少个 React 组件或钩子。
parseSettingsFile() 始终在从文件缓存返回之前进行克隆。 这可以防止调用者无意中改变缓存条目(例如, mergeWith 改变它的第一个参数)。
6. 变化检测
当 Claude Code 运行时,设置文件可能会更改。 这 settingsChangeDetector (in changeDetector.ts) 监视文件 chokidar 并每 30 分钟轮询一次 MDM 注册表/plist。
文件观看架构
关键常数
const FILE_STABILITY_THRESHOLD_MS = 1000 // wait for write to stabilize const FILE_STABILITY_POLL_INTERVAL_MS = 500 // chokidar awaitWriteFinish const INTERNAL_WRITE_WINDOW_MS = 5000 // suppress own writes const MDM_POLL_INTERVAL_MS = 30 * 60 * 1000 // 30 min MDM registry poll const DELETION_GRACE_MS = 1700 // absorb delete-and-recreate
内部写入——抑制自触发的重新加载循环
当 Claude Code 本身写入设置文件(例如,保存权限规则)时,它会调用 markInternalWrite(filePath) 在写作之前。 当乔基达尔发射 change 事件, consumeInternalWrite(path, 5000) 检测到更改源自内部并默默地跳过重新加载。 这可以防止每次 Claude Code 更新其自己的设置时重新加载级联。
// updateSettingsForSource() before file write: markInternalWrite(filePath) writeFileSyncAndFlush_DEPRECATED(filePath, jsonStringify(updatedSettings, null, 2)) resetSettingsCache()
删除并重新创建宽限期
自动更新程序和某些编辑器会删除文件,然后自动重新创建它。 天真地,这将触发“设置已删除”通知,然后立即触发“设置已添加”通知。 变化检测器使用 1700ms宽限期:当文件被删除时,它会在处理删除之前等待。 如果一个 add or change 事件在宽限窗口内到达,删除将被取消并被视为正常更改。
fanOut() — 单生产者模式
在当前架构之前,每个订阅者都调用 resetSettingsCache() 收到变更通知后采取防御措施。 对于 N 个订阅者,这会导致每次更改进行 N 次缓存清除和 N 次磁盘重新加载。 解决方案是将重置集中在一个单一的 fanOut() function:
function fanOut(source: SettingSource): void { resetSettingsCache() // one clear settingsChanged.emit(source) // N subscribers; first one pays the disk miss // subsequent ones hit the repopulated cache }
7. MDM 和基于文件的托管设置
The policySettings 层有四个内部子源。 第一个具有非空内容的获胜 - 其他的在本次会话中将被忽略。
政策子源优先级
Windows:HKLM\SOFTWARE\Policies\ClaudeCode 仅限管理员写入
Managed-settings.json 的平台路径
| Platform | 基本路径 | 插入目录 |
|---|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json |
/Library/Application Support/ClaudeCode/managed-settings.d/ |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json |
C:\Program Files\ClaudeCode\managed-settings.d\ |
| Linux | /etc/claude-code/managed-settings.json |
/etc/claude-code/managed-settings.d/ |
插入目录合并顺序
The managed-settings.d/ 目录使多个团队能够交付独立的策略片段,而无需编辑单个共享文件。 文件已排序 alphabetically 并按顺序合并——后面的文件覆盖前面的文件。 这遵循 systemd/sudoers 约定。
// settings.ts — loadManagedFileSettings() // 1. Load managed-settings.json as base (lowest precedence) const { settings } = parseSettingsFile(getManagedSettingsFilePath()) // 2. Load and sort drop-in files const entries = fs.readdirSync(dropInDir) .filter(d => d.isFile() && d.name.endsWith('.json')) .map(d => d.name) .sort() // alphabetical: 10-otel.json, 20-security.json … for (const name of entries) { merged = mergeWith(merged, parseSettingsFile(join(dropInDir, name)).settings, ...) }
MDM 原始读取 — 启动并行性
读取 macOS plist (plutil -convert json)和 Windows 注册表(reg query)需要生成子进程。 这些在启动期间(在模块初始化完成之前)尽早触发,因此子进程与模块加载并行运行。 到......的时候 ensureMdmSettingsLoaded() 等待时,结果通常已经可用。
// mdm/settings.ts export function startMdmSettingsLoad(): void { mdmLoadPromise = (async () => { const rawPromise = getMdmRawReadPromise() ?? fireRawRead() const { mdm, hkcu } = consumeRawReadResult(await rawPromise) mdmCache = mdm hkcuCache = hkcu })() }
8. 远程管理设置
对于企业/团队订阅者和所有控制台(API 密钥)用户,Claude Code 在启动时从 Anthropic API 获取设置。 这是 highest-priority 内的子源 policySettings.
Eligibility
- 控制台用户(API 密钥): 总是有资格
- OAuth 用户 — 企业或团队: eligible
- OAuth users — 未知订阅类型 (外部注入代币,CI):符合条件 — API 为不符合条件的组织返回空设置,因此成本是额外的一次往返
- 第三方API提供商 or 自定义基本 URL: ineligible
- 联合办公(
local-agententrypoint): ineligible
获取生命周期
基于 ETag 的缓存(校验和)
为了避免在每次启动时重新下载未更改的设置,Claude Code 计算缓存设置的 SHA-256 校验和并将其作为 If-None-Match 标头。 服务器返回 304 Not Modified 如果什么都没有改变。 校验和算法与服务器的 Python 实现完全匹配:
// Must match Python: json.dumps(settings, sort_keys=True, separators=(",", ":")) export function computeChecksumFromSettings(settings: SettingsJson): string { const sorted = sortKeysDeep(settings) // recursive key sort const normalized = jsonStringify(sorted) // no spaces after separators const hash = createHash('sha256').update(normalized).digest('hex') return `sha256:${hash}` }
故障开放设计
- 如果获取失败(网络错误、超时、身份验证错误)并且缓存文件存在→使用陈旧的缓存
- 如果获取失败且不存在缓存 → 无需远程设置即可继续
- 验证错误(
401/403) 做 not retry - 网络/超时错误使用指数退避重试最多 5 次
204/404→ 未配置任何设置 → 删除过时的缓存文件
危险变更的安全检查
当远程设置到达时内容与缓存不同时, checkManagedSettingsSecurity() 在应用它们之前运行。 如果传入的设置被认为是危险的(例如,新的挂钩、更改的权限规则),则会提示用户批准。 拒绝会保留之前缓存的设置。
后台轮询
初始加载后,后台间隔轮询 API 每 60 分钟一班 (POLLING_INTERVAL_MS = 60 * 60 * 1000)。 在每次轮询中,它都会检查设置是否更改(JSON 字符串比较)并调用 settingsChangeDetector.notifyChange('policySettings') 只要他们这样做了。 间隔是用以下命令创建的 .unref() 所以它不会阻止进程退出。
9. 设置同步(CCR)
设置同步(services/settingsSync/) 是独立于远程管理设置的机制。 它同步一个 用户自己的 跨环境的设置 - 主要是在存储库 (CCR) 无头模式下的交互式 CLI 和 Claude Code 之间。
| Direction | Trigger | 同步了什么 |
|---|---|---|
| Upload (CLI → 云) | 交互式 CLI 启动(preAction) |
用户settings.json、用户CLAUDE.md、本地settings.local.json、项目CLAUDE.local.md |
| Download (云 → CCR) | CCR 无头启动(插件安装之前) | 相同的四个文件,由文件路径 + git 远程哈希键控 |
同步键
export const SYNC_KEYS = { USER_SETTINGS: '~/.claude/settings.json', USER_MEMORY: '~/.claude/CLAUDE.md', projectSettings: (projectId: string) => `projects/${projectId}/.claude/settings.local.json`, projectMemory: (projectId: string) => `projects/${projectId}/CLAUDE.local.md`, }
项目特定的密钥由 git 远程 URL 的 SHA 确定范围,因此设置 github.com/org/repo-A 切勿覆盖设置 github.com/org/repo-B.
MAX_FILE_SIZE_BYTES)。 超过此限制的文件将被静默跳过。 该限制对上传和下载路径均强制执行。
10. 安全限制
几个设置键有意排除 projectSettings 来自他们的信任域。 否则,恶意存储库可能会自动注入危险设置。
| 设置/检查 | 来源可信 | 为什么projectSettings被排除 |
|---|---|---|
skipDangerousModePermissionPrompt |
用户、本地、标志、策略 | 存储库可以自动绕过危险模式对话框(RCE 风险) |
skipAutoPermissionPrompt |
用户、本地、标志、策略 | 相同 - 自动模式选择必须由用户驱动 |
useAutoModeDuringPlan |
用户、本地、标志、策略 | 相同——计划模式语义是安全关键的 |
autoMode 分类器配置 |
用户、本地、标志、策略 | 通过存储库注入允许/拒绝规则将是一个 RCE 向量 |
true 在托管设置中, only 遵守托管设置中的允许/拒绝/询问规则。 所有用户、项目、本地和 CLI 参数权限规则都会被静默忽略。 这是企业控制,用于防止员工授予自己额外的权限。
11. 要点
- 设置按顺序从 5 层合并: 用户→项目→本地→标志→策略。 后面的层会覆盖前面的层。 数组是跨层去重连接的。
- policySettings 在内部使用first-source-wins:远程击败 MDM (plist/HKLM) 击败 Managed-settings.json 击败 HKCU。 仅应用第一个非空源。
- The 三层缓存 (会话、每个源、每个文件)保持快速启动。 单个
resetSettingsCache()使所有三个无效,并在通知订阅者之前以原子方式调用。 - 变化检测 使用 chokidar 文件监视用户/项目/本地/策略文件以及 30 分钟的 MDM 轮询。 使用 5 秒窗口抑制内部写入,以防止重新加载循环。
- 远程管理设置 是最高优先级的策略子源,在启动时使用 ETag 缓存获取,最多重试 5 次,每小时轮询一次。 系统总是无法打开 - 失败的提取会回退到缓存的文件或只是跳过远程设置。
- 设置同步 与远程管理设置正交。 它同步 用户自己的 使用云键值存储在 CLI 和 CCR 之间设置文件(不是企业策略),范围由 git 远程哈希确定。
- 几个安全敏感标志(
skipDangerousModePermissionPrompt、自动模式选择加入、分类器规则)有意排除projectSettings以防止恶意存储库升级自己的权限。 - The 向后兼容合约 SettingsSchema 中禁止删除字段或枚举值。 新字段必须是可选的。 在 Zod 验证之前,无效的个人权限规则将被删除,因此一条错误的规则无法使整个设置文件无效。