01 它不只是偏好文件
大多数人第一次接触 settings.json,是因为想改个主题或换个模型。文件看起来也确实无害:
json
{
"model": "opus",
"theme": "dark"
}
但翻开完整的 JSON Schema 会发现,这个文件有一百多个顶层键,覆盖的范围远超「偏好」:哪些命令可以不经确认直接执行、写文件前后要触发什么自动化、子进程能访问哪些域名、环境变量里的密钥要不要对沙箱隐藏、企业管理员如何强制登录方式和最低版本。
Claude Code 是一个能执行任意命令的 AI 代理。代理的行为是概率性的,而 settings.json 是把它圈进确定性护栏的主要机制。理解这个文件,本质上是在回答三个问题:我允许它做什么(permissions、sandbox),我要求它必须做什么(hooks),谁有权决定前两者(分层与企业管控)。
02 五层配置与优先级
同名的 settings.json 会出现在多个位置,按固定顺序加载与覆盖。从高到低:
| 层级 | 名称 | 说明 |
|---|---|---|
| L1 | 企业托管 | managed-settings.json,管理员通过 MDM 或系统目录下发,用户不可覆盖 |
| L2 | 命令行参数 | claude --settings 传入,作用于当前会话 |
| L3 | 项目本地 | .claude/settings.local.json,个人专用,不进 Git |
| L4 | 项目共享 | .claude/settings.json,提交进仓库,对全团队生效 |
| L5 | 用户全局 | ~/.claude/settings.json,跟人走,对所有项目生效 |
企业托管文件的位置随平台不同:
| 平台 | 路径 |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json,也支持通过 Jamf 等 MDM 下发 com.anthropic.claudecode plist |
| Linux / WSL | /etc/claude-code/managed-settings.json |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json,或组策略注册表 HKLM\SOFTWARE\Policies\ClaudeCode |
各平台还支持 managed-settings.d/ 拆分目录,多个策略文件按字母序合并,方便不同团队分管不同策略片段。
合并语义:比「高层覆盖低层」更细
标量值(如 model、theme)遵循简单覆盖:高层赢。但有三类例外,恰恰是这个体系的设计精髓:
- 权限数组跨层合并。
permissions.allow / deny / ask不是覆盖关系,各层规则叠加生效,且 deny 永远优先。团队在项目级放行Bash(npm *),你仍可在个人层 deny 掉其中一部分。 - 「任一层为 true 即生效」的开关。 例如
disableClaudeAiConnectors:项目可以为自己关掉云连接器,但项目级的false无法推翻用户级的true。 - 只认托管层的键。
forceLoginMethod、requiredMinimumVersion、allowManagedHooksOnly等键写在用户或项目文件里会被直接忽略,只有 L1 有资格声明。
把这三条放在一起看,能读出一条贯穿全篇的设计原则:收紧可以来自任何一层,放宽只能来自更高层。 后文的沙箱、MCP、插件治理都在重复这个模式。
容易踩的坑 某一层的 JSON 语法写坏,不会报错弹窗,而是该文件的全部设置静默失效 。改完配置用
jq . settings.json或编辑器校验一遍,是性价比最高的习惯。给文件加上"$schema": "https://json.schemastore.org/claude-code-settings.json"还能获得编辑器自动补全。
03 permissions:给代理画边界
权限系统由三个规则数组和一个默认模式组成:
json
{
"permissions": {
"allow": ["Bash(git *)", "Read", "Edit(src/**)"],
"deny": ["Read(.env)", "Read(secrets/**)"],
"ask": ["Bash(git push*)"],
"defaultMode": "default",
"additionalDirectories": ["../shared-libs"]
}
}
裁决顺序固定:deny → ask → allow。命中 deny 直接拒绝,且 deny 规则跨所有配置层合并,任何一层都拦得住。
规则语法
| 形式 | 示例 | 含义 |
|---|---|---|
| 裸工具名 | Read |
放行该工具的全部调用 |
| 精确匹配 | Bash(npm run test) |
只匹配这条命令 |
| 前缀通配 | Bash(git *) |
匹配以 git 开头的命令;Bash(git:*) 是等价写法 |
| 路径规则 | Edit(docs/**) |
Read / Edit 类工具用 gitignore 风格模式;//path 为绝对路径,~/path 相对家目录,/path 相对 settings 文件所在位置 |
| 域名规则 | WebFetch(domain:*.github.com) |
限定网络抓取的目标域,通配符只在点分段内匹配 |
| MCP 工具 | mcp__github__get_issue |
命名规则为 mcp__服务器名__工具名 |
几个实践细节值得单独说:
- Windows 上 PowerShell 是独立工具 ,规则要写成
PowerShell(git status*)而不是Bash(...)------照抄网上的类 Unix 配置是 Windows 用户权限「不生效」的头号原因。 - 星号前的空格有语义。
Bash(ls *)要求词边界,匹配ls -la但不匹配lsof;Bash(ls*)两者都匹配。写宽了就是给自己挖洞。 - 复合命令逐段判定。 Claude Code 识别
&&、;、管道等操作符,Bash(git *)不会连带放行git status && rm -rf /,每一段都要各自过规则;点「不再询问」保存的也是逐段的多条规则。timeout、nice这类进程包装器会被剥掉后再匹配。
defaultMode:没有命中任何规则时怎么办
| 模式 | 行为 |
|---|---|
default |
每类首次使用弹窗确认,标准交互(新版界面标注为 Manual) |
acceptEdits |
自动接受文件编辑及 mkdir、mv 等安全的文件系统命令,其余命令仍确认 |
plan |
只读分析,不执行修改,适合方案评审 |
auto |
由一个分类器模型判断动作风险,安全操作放行、危险操作拦下;可用 autoMode 键定制分类规则 |
dontAsk |
不询问,未被规则明确允许的操作直接拒绝 |
bypassPermissions |
跳过确认,仅保留 ask 规则和删根目录这类熔断防线。只应在容器或一次性虚拟机里用,企业可用 disableBypassPermissionsMode: "disable" 一票禁用 |
04 hooks:把确定性塞回去
提示词只能「建议」模型做事,hooks 则是在生命周期的固定节点无条件执行的钩子------每次写完文件必定跑 formatter,每条 bash 命令必定留审计日志。这是把不可协商的规则从「说服模型」变成「架构保证」。
结构是三层嵌套:事件 → 匹配器 → 钩子列表。
json
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read -r f; prettier --write \"$f\"; } 2>/dev/null || true"
}]
}]
}
}
事件:远不止「工具前后」
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
PreToolUse |
工具执行前,可拦截、可改写输入 | 安全门禁、参数重写 |
PostToolUse |
工具成功后 | 格式化、跑测试、审计 |
PostToolUseFailure |
工具失败后 | 失败告警 |
UserPromptSubmit |
用户提交输入时 | 注入上下文、拦截敏感词 |
PermissionRequest |
弹权限确认框之前 | 自动化审批策略 |
Stop / SubagentStop |
主代理 / 子代理结束回合 | 完成度校验,不达标可打回重做 |
SessionStart / SessionEnd |
会话启停 | 装载环境、清理现场 |
PreCompact / PostCompact |
上下文压缩前后 | 保护关键信息 |
Notification |
产生通知时 | 转发到 IM、系统通知 |
schema 里还有一批更新的事件------WorktreeCreate、FileChanged、TeammateIdle、ConfigChange 等,共三十余种,hooks 已经从「工具回调」长成了完整的生命周期事件总线。
五种钩子类型
多数文章只写 command 一种,实际有五种,能力差异很大:
| 类型 | 执行方式 | 适用场景 |
|---|---|---|
command |
运行 shell 命令,stdin 收 JSON | 格式化、日志、门禁脚本 |
prompt |
调用一个快速 LLM 判断是/否 | 「这条命令安全吗」这类无法用正则表达的判断 |
agent |
起一个带工具的子代理去验证 | 「确认测试真的跑过且通过」 |
http |
把事件 JSON POST 到指定 URL | 接入企业审计与合规平台 |
mcp_tool |
调用已配置的 MCP 服务器工具 | 复用现有集成,支持 ${tool_input.file_path} 插值 |
输入输出协议
钩子从 stdin 读到完整的事件 JSON(session_id、tool_name、tool_input 等),通过两条通道回话:
- 退出码。
0表示放行;2表示阻断,stderr 的内容会喂回给模型让它修正;其他退出码视为非阻断错误,只展示给用户。注意1也属于「非阻断」------想强制拦截必须显式exit 2,这与 Unix 惯例相反。输出上限 10000 字符。 - stdout 输出 JSON ,做精细控制。PreToolUse 钩子可以返回
permissionDecision: "allow" | "deny" | "ask"直接裁决权限,甚至用updatedInput改写工具的入参 ;任何钩子可以用additionalContext把文本注入回模型上下文,用systemMessage给用户弹提示。
一个用 JSON 输出做门禁的例子------禁止任何工具读取环境文件:
json
{
"hooks": {
"PreToolUse": [{
"matcher": "Read|Bash",
"hooks": [{
"type": "command",
"command": "python guard_env_files.py"
}]
}]
}
}
json
// guard_env_files.py 检测到目标含 .env 时输出:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Env files are off-limits. Read .env.example instead."
}
}
修饰字段同样值得关注:if 用权限规则语法先过滤(如 "if": "Bash(git *)",避免每条命令都起进程);async 后台执行不阻塞;once 执行一次即移除;timeout 与 statusMessage 控制体验;args 数组则以 exec 形式直接 spawn 可执行文件,参数不经过 shell 解析,天然免疫注入。
安全提醒 hooks 以当前用户的完整权限执行任意命令,项目级 hooks 会随仓库分发给每个克隆代码的人。审阅不熟悉仓库的
.claude/settings.json应当像审阅 CI 脚本一样认真。个人侧可用disableAllHooks一键停用,企业侧可用allowManagedHooksOnly只保留管理员下发的钩子。
05 模型与推理策略
json
{
"model": "claude-fable-5[1m]",
"fallbackModel": ["opus", "default"],
"effortLevel": "xhigh",
"alwaysThinkingEnabled": true,
"autoCompactWindow": 200000
}
model接受别名(opus、sonnet)或完整 ID,后缀[1m]申请百万级长上下文窗口。fallbackModel是一个按序尝试的数组,主模型过载时自动降级,"default"展开为当前默认模型。它是少数不跨层合并的数组:优先级最高的那份配置提供完整的降级链。effortLevel(low / medium / high / xhigh)控制推理深度,与alwaysThinkingEnabled配合决定「想多久再动手」。- 企业侧有一组对应的管控键:
availableModels白名单限定可选模型,enforceAvailableModels连默认模型也纳入约束,modelOverrides把 Anthropic 模型 ID 映射到 Bedrock 推理配置 ARN 等私有部署目标。
06 sandbox:操作系统级隔离
permissions 决定「允不允许执行」,sandbox 决定「执行时能碰到什么」。它在 macOS 用 Seatbelt、Linux 用 bubblewrap 实现真正的 OS 级隔离,三个维度各自收紧:
json
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"],
"deniedDomains": ["*.internal.corp"],
"allowLocalBinding": true
},
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"],
"allowWrite": ["./build"]
},
"credentials": {
"envVars": [
{ "name": "NPM_TOKEN", "mode": "mask",
"injectHosts": ["registry.npmjs.org"] },
{ "name": "PROD_DB_PASSWORD", "mode": "deny" }
]
}
}
}
最精巧的是凭证掩码。mode: "mask" 让沙箱内的进程只能看到一个哨兵值,出口代理在请求发往 injectHosts 白名单域名时才把真值换进去------命令能正常用 token 发布包,却自始至终拿不到 token 本身 ,即使模型被诱导执行 echo $NPM_TOKEN 也只会打出占位符。deniedDomains 从所有配置层合并且优先于 allow,再次呼应「收紧无需授权,放宽必须升级」的原则。
autoAllowBashIfSandboxed 则揭示了 sandbox 与 permissions 的联动逻辑:既然命令已经被物理隔离,就不必每条都弹窗------用隔离换流畅,比裸奔的 bypassPermissions 安全得多。
07 MCP 与插件治理
MCP 服务器和插件都是往代理里注入第三方代码的通道,settings.json 为两者各配了一套「个人选择 + 企业裁决」的双层开关。
个人与团队层面:enabledMcpjsonServers / disabledMcpjsonServers 逐个批准或拒绝项目 .mcp.json 里声明的服务器,enableAllProjectMcpServers 整体放行;插件用 enabledPlugins 以 插件名@市场名 为键启停,extraKnownMarketplaces 在项目里登记团队私有市场,克隆仓库即获得完整插件源。
企业层面的键则全部只在托管层生效:
allowedMcpServers/deniedMcpServers:按名称、启动命令或 URL 通配符做白/黑名单,两边都命中时黑名单赢;strictKnownMarketplaces/blockedMarketplaces:限定插件市场来源,校验发生在下载之前,被拒来源不会落盘;disableSideloadFlags:封掉--plugin-dir、--mcp-config等 CLI 旁路,堵住绕过白名单的口子;strictPluginOnlyCustomization:更进一步,把 skills、agents、hooks、mcp 四类本地自定义全部锁死,只允许来自受管插件。
精度提示 要在本机关掉项目启用的插件,应写进
.claude/settings.local.json。写在用户全局层没用------优先级是 user < project < local,全局层的false会被项目层的true覆盖。
08 企业管控专用键
一批键只有写在 managed-settings.json 里才生效,它们共同把 settings.json 变成了一个 MDM 策略载体:
| 键 | 作用 |
|---|---|
forceLoginMethod / forceLoginOrgUUID |
强制登录方式,并要求账号属于指定组织 |
requiredMinimumVersion / requiredMaximumVersion |
版本围栏,越界直接拒绝启动 |
claudeMd |
以组织记忆的形式注入一段 CLAUDE.md 指令,全员生效 |
allowManagedPermissionRulesOnly |
只认托管层的权限规则,用户/项目/CLI 的规则全部失效 |
allowManagedHooksOnly |
只运行托管层下发的 hooks |
availableModels + enforceAvailableModels |
模型白名单,含默认模型兜底改写 |
disableAgentView / disableRemoteControl |
关闭后台代理与远程控制入口 |
pluginTrustMessage / companyAnnouncements |
插件安装警示附言、启动横幅公告 |
配合第 2 节的合并语义看,整个治理链条是闭环的:管理员用 strictKnownMarketplaces 圈定插件来源,用 disableSideloadFlags 封 CLI 旁路,用 allowManagedHooksOnly 保证审计钩子不被本地配置顶掉------每一个「用户侧自由度」都有对应的「管理侧闸门」。
09 体验层配置
治理之外,settings.json 也管日常手感。几个高频项:
statusLine:接一个命令自定义状态栏,会话上下文以 JSON 从 stdin 传入,可用refreshInterval定时刷新------不少人用它显示当前分支、消耗 token 数。attribution:定制或清空 commit / PR 里的 Claude 署名(置空串即隐藏),取代已废弃的includeCoAuthoredBy。env:为所有会话注入环境变量,适合放代理地址、遥测开关这类跟人走的变量。outputStyle、language、theme、editorMode(vim 党的"vim")、spinnerVerbs(自定义等待动词,纯粹的乐趣项)。cleanupPeriodDays:会话记录保留天数,默认 30。
这类简单标量不必手改文件,交互式的 /config 命令更稳妥;手改 JSON 的价值在 hooks、权限数组这类结构化配置上。
10 分层实践与排错清单
什么放哪一层
三个文件的分工,用「这条配置该跟着谁走」来判断:
json
// ~/.claude/settings.json ------ 跟人走:偏好与通用只读放行
{
"model": "claude-fable-5[1m]",
"effortLevel": "xhigh",
"permissions": {
"defaultMode": "auto",
"allow": [
"PowerShell(git status*)", "PowerShell(git diff*)",
"PowerShell(git log*)", "PowerShell(Get-ChildItem *)",
"PowerShell(Get-Content *)", "PowerShell(Test-Path *)"
]
}
}
json
// .claude/settings.json ------ 跟仓库走:团队约定,提交进 Git
{
"permissions": {
"allow": ["Bash(npm run lint)", "Bash(npm run test *)"],
"deny": ["Read(.env*)", "Read(secrets/**)"]
},
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{ "type": "command",
"command": "jq -r '.tool_input.file_path' | { read -r f; npx prettier --write \"$f\"; } 2>/dev/null || true" }]
}]
}
}
json
// .claude/settings.local.json ------ 跟机器走:个人差异,不进 Git
{
"permissions": {
"allow": ["mcp__claude_ai_Microsoft_Learn__microsoft_docs_search"]
},
"env": { "HTTP_PROXY": "http://127.0.0.1:7890" }
}
上面用户层的例子来自一台真实的 Windows 开发机------注意所有命令规则都是 PowerShell(...),且只放行只读操作:查看状态、看日志、列目录。写操作留给 auto 模式的分类器把关。这是一个值得照抄的基线:允许列表只放「看」,「动」的部分交给模式与确认。
排错清单
| 症状 | 最常见原因 | 处置 |
|---|---|---|
| 某文件的设置全部不生效 | JSON 语法损坏,静默失效 | jq . 文件路径 校验;加 $schema 让编辑器实时报错 |
| 权限规则不匹配 | Windows 上写了 Bash(...);或工具名大小写不对 |
对照实际弹窗里的工具名写规则,Windows 命令用 PowerShell(...) |
| 新加的 hook 不触发 | 配置监视器只监听会话启动时已存在 settings 文件的目录 | 执行 /hooks 重载配置,或重启会话 |
| hook 触发但没效果 | 命令在 hook 环境下不可用(PATH、包管理器差异) | 手工构造 stdin JSON 管道测试命令本身;claude --debug 看执行日志 |
| 改配置时丢了原有规则 | 数组被整体替换而非合并 | 先读后写:编辑前完整读取原文件,追加而非覆盖数组 |
| local 文件被误提交 | 由 Claude Code 创建时会自动加入 gitignore,手工创建的不会 | 确认 .claude/settings.local.json 在 .gitignore 里 |
结语
settings.json 的一百多个键,归纳起来只在回答一个问题:当一个能写文件、能跑命令、能联网的 AI 代理进入你的开发流程,信任的边界画在哪里,由谁来画。个人用它换取流畅,团队用它固化约定,企业用它落实策略------而「收紧可以来自任何一层,放宽只能来自更高层」这条原则,让三者可以叠加共存而互不拆台。下一次打开这个文件,不妨先想清楚要改的这一行属于谁,再决定它落在哪一层。