Claude Code 实战入门(九):settings.json、三层权限模型、env
settings.json 决定了 Claude 能动什么、在哪里、用谁的身份。三层(用户、项目、本地)、权限语法、改变行为的环境变量、以及那条第一次都会咬人的优先级规则。
如果说 Hooks 是你伸手进 Claude Code 的方式,settings.json 就是你先告诉它能碰什么的地方。它也是那个会用优先级规则咬人的文件。
这一篇是缺失的那份参考手册。
三层
Claude Code 会按顺序读三份 settings.json:
- 用户级 —
~/.claude/settings.json。对你这台机器上的所有项目生效。 - 项目级 —
<repo>/.claude/settings.json。提交到 Git,对在这个仓库里干活的所有人生效。 - 本地级 —
<repo>/.claude/settings.local.json。被 gitignore,是你针对这个仓库的私人覆盖。
合并规则:后面的层逐 key 覆盖前面的。但权限里的 allow 是叠加的,deny 是减法的——任何一层 deny 之后,没有任何其他层能再 allow 回来。这种不对称就是这套系统安全的关键。
实际后果:把组织策略放 ~/.claude/settings.json,把项目规则放 .claude/settings.json(提交的那份),把"我在这台机器上信任这一件事"的覆盖放 .claude/settings.local.json。
permissions 块——语法
| |
括号里是按工具语义来的 glob 匹配:
Read(path-glob)— 文件路径模式。Edit(path-glob)— 同上。Bash(command-pattern)— 第一个 token 必须匹配。*用得要小心:Bash(git *)等于把git push --force也放过了。写得更具体。WebFetch(domain:host)— 只匹配 host,不带路径。
裸写 Read 或 Bash = 整个工具放过。除了在你完全信任的个人 ~/.claude/settings.json 里之外,这几乎总是太宽。
为什么 deny 永远赢
合并后的配置一旦在任何位置 deny 了某件事,没有任何其他位置能再 allow 回来。这就是你想要的那根杠杆。
例。仓库的 .claude/settings.json 写:
| |
队友在 .claude/settings.local.json 加 "allow": ["Bash(git push origin main)"]。push 不会被放行。 项目层的 deny 赢。这是对的,你应该靠它。
env——另外一半
env 块给每一次工具调用注入环境变量:
| |
两件事要知道:
- 这些变量给 Bash 和任何继承环境的 hook 脚本用。它们不会进入模型的 prompt。所以放
*_API_KEY是安全的。 - 本地层覆盖项目层覆盖用户层。所以在
.claude/settings.local.json里写DEBUG=true,只对你打开日志,不会被提交到仓库。
hooks——在同一个文件里引用
| |
matcher 是用竖线分隔的工具名。三层的 hooks 全部会被运行;hooks 没有"覆盖",深层加 hook 是叠加,不是替换。
一份真实仓库里的 settings.json
下面是我自己一个项目的项目级配置,做了少量打码:
| |
三件事值得注意:
- Bash 的 allow 列表收了只读和可逆的 git 命令,但永远没有
push、reset --hard、rebase -i。Push 是人来按的动作。 Edit(.github/workflows/**)被 deny。CI 配置改动需要 review,不能跟着普通 commit 溜走。- Hooks 给 deny 列表上双保险。万一 deny 规则有 typo,hook 仍然会拦下危险调用。
优先级——按顺序的清单
行为不符合预期时按这个顺序查:
- 是否在任何
deny里?→ 拦截,不管 allow 怎么写。 - 是否在任何
allow里?→ 放行。 - 否则 → Claude 做之前会问你。
想知道哪条规则赢了,加 --debug 跑一遍,读权限解析日志。它会告诉你哪一文件、哪一行给出了判决。
收尾
settings.json 是这个项目里 Claude 行为的宪法。Deny 写得短而狠,allow 写得具体,hooks 当第二道防线。把分层和优先级钉进脑子里,配新仓库九十秒就够。在那之前规则会让你觉得任性;它们不是。