2343 字
约 7 分钟
20
S24-X05 · settings.json:用户级与项目级配置怎么分工
~/.claude/settings.json 和 .claude/settings.json 名字一样,角色完全不同:前者是你个人的默认值,后者是这个仓库的团队契约。分错地方的代价很具体——规则写到用户级,同事永远拿不到;写到项目级又掺了个人路径,别人一跑就报错。
这篇讲四件事:合并语义、常用键、权限规则怎么写、团队怎么落地,最后附上五个静默失效的坑。
1. 四个来源与合并顺序
| 优先级 | 来源 | 位置 | 谁改得动 |
|---|---|---|---|
| 最高 | 企业管理策略 | 管理员下发 | 普通用户不能 |
| 高 | 命令行参数 / 进程环境 | 启动时 | 当前用户 |
| 中 | 项目 settings.local.json |
.claude/ |
当前用户,不进 git |
| 中低 | 项目 settings.json |
.claude/ |
团队,进 git |
| 最低 | 用户 settings.json |
~/.claude/ |
当前用户,所有项目 |
结论和方法论:
- 越具体越优先:项目覆盖用户,局部覆盖全局。这是"我在用户级改了没用"的第一解释。
- 用户级放偏好,项目级放契约。反过来就会出现"规则只在我机器上生效"。
- 企业管理策略是天花板,个人改不动它,这正好用来放"必须遵守"的那几条。
2. 合并语义:别指望两层拼起来
官方描述是"合并,更具体的层级优先"。实际配置时按下面理解最安全:
| 类型 | 行为 | 建议 |
|---|---|---|
标量键(model、outputStyle) |
高层覆盖低层 | 只在需要覆盖时写 |
对象(env、statusLine) |
按键合并,同名键覆盖 | 不同层写不同键,别指望拼接同一键的片段 |
数组(permissions.allow 等) |
倾向叠加 | 想知道确切结果就实测 |
# 想知道某一层到底贡献了哪些键,直接看文件本身
jq -S . ~/.claude/settings.json
jq -S . .claude/settings.json
jq -S . .claude/settings.local.json
想验证真实生效结果,最快的方式是改一条能观察到的规则(比如临时加一条 deny),在会话里试触发,比读文档靠得住。数组到底叠加还是覆盖,各版本有调整,按当前版本核对。
3. 常用配置项
| 键 | 作用 | 建议放哪一级 |
|---|---|---|
permissions.allow / deny / ask |
工具授权规则 | 项目级放团队规则,用户级放个人习惯 |
model |
默认模型 | 用户级 |
env |
注入会话环境变量 | 项目级(非敏感项),密钥走外部 |
hooks |
生命周期钩子 | 项目级,团队共享 |
statusLine |
状态栏命令与展示 | 用户级 |
outputStyle |
输出风格 | 用户级 |
| 其他键 | —— | 以当前版本的设置文档为准,键名会调整 |
{
"model": "MODEL_ID",
"permissions": {
"allow": ["Bash(pnpm test:*)", "Read(./src/**)"],
"ask": ["Bash(git commit:*)"],
"deny": ["Bash(git push --force:*)"]
},
"env": { "NODE_ENV": "development" },
"statusLine": { "type": "command", "command": "~/.claude/status.sh" }
}
写错键名不会报错,只会被忽略——这是 settings 最容易吃亏的地方(见第 8 节)。
4. 权限规则:工具名 + 参数模式
基本形态两种:Tool(整个工具放行/禁止)与 Tool(specifier)(按参数匹配)。
| 写法 | 匹配对象 | 说明 |
|---|---|---|
Bash |
所有 shell 命令 | 范围最大,慎用 |
Bash(npm run test:*) |
以该前缀开头的命令 | :* 是前缀匹配,不是正则 |
Read(./src/**) |
仓库内路径 | * 一层,** 递归 |
Edit(./docs/**) |
可编辑路径 | 与 Read 分开写 |
WebFetch(domain:example.com) |
域名 | 不带协议 |
mcp__server__tool |
MCP 工具 | 双下划线分隔 |
判定顺序要记牢:deny 优先于 allow。同一条命令同时命中 allow 与 deny,结果是拒绝。ask 是中间档——不直接拒,但要你点确认。
| 场景 | 写法 | 效果 |
|---|---|---|
| 只允许跑测试 | allow: ["Bash(pnpm test:*)"] |
其他 Bash 仍需确认 |
| 禁止强推 | deny: ["Bash(git push --force:*)"] |
直接拦 |
| 提交前问一句 | ask: ["Bash(git commit:*)"] |
每次弹确认 |
| 禁止读密钥 | deny: ["Read(./.env)", "Read(./**/*.pem)"] |
读不到 |
5. 把危险命令写进 deny
{
"permissions": {
"deny": [
"Bash(rm -rf:*)",
"Bash(rm -fr:*)",
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git reset --hard:*)",
"Bash(sudo:*)",
"Bash(chmod 777:*)",
"Bash(/etc/:*)",
"Read(./.env)",
"Read(./**/*.pem)",
"Read(./**/id_rsa)"
]
}
}
必须清楚这套规则的边界:前缀匹配防的是手滑,不是恶意绕过。rm -rf(两个空格)、/bin/rm -rf、rm -r -f、把命令塞进脚本再执行,都能绕开。所以:
- 真正的防线是代码评审 + 分支保护 + 备份;
deny的价值在于"让模型在正常路径上不做出格动作",把摩擦放在正确的位置;- 需要强隔离时,用容器/受限账号,而不是堆规则。
6. settings.local.json 用在哪
{
"permissions": {
"allow": ["Bash(./scripts/dev.sh:*)"]
},
"env": { "LOCAL_SDK": "/opt/sdk" }
}
它的三个正当用途:
- 本机才有的路径(SDK、工具链、脚本);
- 个人额外放行的命令(不想让团队继承你的宽松规则);
- 实验性开关(试完就删,别留成"幽灵配置")。
它不该承担的两件事:放密钥(换成环境变量)、放团队规则(别人拿不到等于没有)。
7. 团队统一配置怎么落地
| 角色 | 改哪个文件 | 流程 |
|---|---|---|
| 团队负责人 | 项目 settings.json |
提 PR,说明"为什么加这条 deny/allow" |
| 每个成员 | settings.local.json |
各自本地,不提交 |
| 新人 onboarding | 只读项目 settings.json |
一句话说明"规则已内置,无需配置" |
| 管理员 | 企业策略 | 需要强制执行的合规项 |
落地五步:
- 项目
settings.json只放团队共识(危险命令 deny、测试命令 allow、hooks); - 个人差异全部下沉到
settings.local.json,并在.gitignore里挡住; - PR 里 review 一次
.claude/下的改动,和 review 代码一样认真; - 新成员入职跑一次
git status,确认没有 local 文件被带进来; - 规则变更写进提交信息,让"为什么禁止"可追溯。
.claude/settings.local.json
8. 排错:五个静默失效
| 现象 | 原因 | 处理 |
|---|---|---|
| 改了配置完全没反应 | JSON 语法错误(尾逗号、注释、中文引号),解析失败被忽略 | jq . .claude/settings.json 先验一遍 |
| 规则写了但没拦住 | 键名拼错(如 permission),未知键被忽略 |
对照当前版本设置文档核对键名 |
| 全部命令都免确认 | 写了 Bash(*) 或 Bash,等于全放行 |
收窄到具体前缀 |
| 路径规则不生效 | * 只匹配一层,跨目录要用 **;或路径基准不是仓库根 |
用 Read(./src/**) 这类显式写法,实测确认 |
| Windows 上规则失效 | 反斜杠与正斜杠混用 | 规则里统一用 / |
jq . .claude/settings.json # 语法体检
jq -r '.permissions.deny[]' .claude/settings.json | head
第 1 条最坑:文件坏了不报错,只表现为"配置像没写"。养成改完就 jq 一遍的习惯,能省掉一整轮怀疑。
9. 改动何时生效
| 改动 | 生效时机 |
|---|---|
permissions 规则 |
新会话生效;会话内以 /permissions 显示为准(按当前版本核对) |
model、outputStyle |
新会话生效 |
hooks |
新会话生效 |
env |
新会话生效,已启动会话不回溯 |
claude
> /permissions # 看当前生效的规则
> /config # 图形化改一部分设置
改完不确定是否生效,最省事的判断是新开一个会话再看。旧会话不回溯,这是设计不是 bug。
10. 边界:哪些事不该用 settings 解决
| 需求 | 更合适的做法 | 理由 |
|---|---|---|
| 必须人人遵守的合规项 | 企业管理策略 | 个人配置改得动 |
| 密钥与 token | 环境变量 / 密钥管理器 | settings 会进 git |
| 复杂流程编排 | hooks 或自定义命令 | settings 只做授权和默认值 |
| 团队文档 | README / wiki | 配置不是文档 |
| 完全隔离的执行环境 | 容器 / 受限账号 | 规则匹配总能被绕过 |
| 临时一次性放行 | 会话内确认 | 写进文件会长期留着 |
一句话:settings 管"默认信任边界",不管"绝对安全"。
11. 自测
- 用户级与项目级都配了
model,最终用哪个?依据是什么? - 一条命令同时命中
allow与deny,结果是什么? Read(./src/*)与Read(./src/**)差在哪?- 改了
deny规则但当前会话照旧执行,怎么判断是不是没生效? settings.json里有个键拼错了,会报错吗?怎么提前发现?- 为什么说 deny 规则"防手滑不防绕过",真正的防线该放在哪?
S24-X05 · settings.json:用户级与项目级配置怎么分工
https://www.clxhxhhr.top/posts/5697/ 评论
0 条
还没有评论,先写一条吧。