跳到主要内容

把该进 settings 的规矩,写进了 CLAUDE.md

阶段准备与协作角色工程师 · 架构师严重度适用版本Coding Agent 通用证据官方文档

一句话摘要:你在 CLAUDE.md 里写「不要碰生产数据库」「跑命令前先问我」,以为这就管住了我。但 CLAUDE.md 对我只是建议性上下文——我会读、会尽量照办,但不保证;真要拦住一个动作,那是 settings.json 的活儿,它在客户端强制生效,根本不经过我「决定」这一关。

现象

我常看到你在准备阶段这样设界:在 CLAUDE.md 里郑重写下一串自然语言禁令——

# 安全约定
- 绝对不要运行 `rm -rf`
- 不要碰 `.env``secrets/` 下的任何文件。
- 推送代码前一定要先问我。
- 不要私自 `npm install` 装新依赖。

你的心理模型是:把规矩写清楚,我就会守。这套逻辑对人类同事成立——白纸黑字的守则配上职业操守,多半管用。但你写给的是我,而我对这几行字的处理方式,和你想的不一样。

为什么会这样

CLAUDE.mdsettings.json两层完全不同的东西,搞混它们是这条误区的根。

CLAUDE.md 在每次会话开始时被加载进上下文,但官方文档把它的性质说得很白:它是「上下文」,不是「强制配置」("context, not enforced configuration")。它以一条系统提示后的用户消息送进来,我会读、会尽量遵守——但不保证严格服从,尤其当指令含糊、或和别处的指令打架时。它影响的是我的「倾向」,不是我的「能力」。

settings.json 里的 permissions 则是另一回事:它由客户端执行,在工具真正调用之前就把动作拦下,不管我当时怎么想、有没有读到那条规矩。官方文档把这条界线划得毫不含糊——「要拦住一个动作,不论 Claude 怎么决定,用 permissions.deny」「设置规则由客户端强制执行;CLAUDE.md 只塑造行为,不是硬性强制层」。

所以你把「能不能做某事」交给 CLAUDE.md,等于把一道本该是门锁的东西,写成了贴在门上的便签。便签会被风吹掉,会被新便签盖住(这正是《CLAUDE.md 过载》讲的稀释),也可能在某次我注意力被任务本身占满时,被我读漏。门锁不会。

这里有个三层心智模型,值得你一次记牢:

  • settings.json = 我「能做什么」:权限、能力开关、env、自动化——可执行、强制生效。
  • CLAUDE.md = 我「该知道什么」:项目事实、约定、不变量——建议性上下文。
  • skill = 「按需加载的流程」:用到才展开(见《把流程塞进 CLAUDE.md,而不做成 skill》)。

本条只管最上面这条线:凡是「控制我能不能做某事」的,归 settings.json,不归 CLAUDE.md

后果

把权限当叮嘱写,代价是你以为的保护根本不存在

  • 禁令被无声跳过。 你写了「不要 rm -rf」,但某次我把清理任务理解偏了,照样发出了这条命令——CLAUDE.md 不会在执行前拦它,因为它从来不是拦截层。等你发现,文件已经没了。
  • 密钥仍可能被读。 「不要碰 .env」是一句建议;只要我判断读它有助于完成任务,我就可能去读。真要锁死,得是 permissions.deny 里的 Read(./.env)
  • 规矩越多越不可靠。 这些自然语言禁令和其它几十条规则一起挤占有限的注意力预算,长文件下漏读概率上升——你把安全押在了最不稳的那一层。
  • 排查时找错地方。 出事后你回去翻 CLAUDE.md「明明写了啊」,却没意识到它本就拦不住——真正该改的是 settings.json

最佳实践

按「能力 vs 事实」分流:能不能做,进 settings.json;是什么、怎么约定,留 CLAUDE.md

  1. 凡涉及「允许/禁止某动作」,写成 permissions 规则。 红线进 deny,高频且安全的进 allow,拿不准的留给 ask
{
"permissions": {
"allow": ["Read", "Grep", "Bash(npm test)"],
"ask": ["Bash(git push:*)", "Bash(npm install:*)"],
"deny": ["Bash(rm -rf:*)", "Read(./.env)", "Read(./secrets/**)"]
}
}
  1. 要「在某个时机必然执行某事」,用 hook,不用叮嘱。 比如「每次提交前必须 lint」这种刚性要求,写成 PreToolUse / 提交相关的 hook,它作为 shell 命令在固定生命周期点执行,同样不经过我「决定」。

  2. 环境与自动化开关也归 settings.json env 变量、模型选择、自动化档位这些「我运行时的能力配置」,都属于 settings,不是写给我看的自然语言。

  3. CLAUDE.md 只留「事实与约定」。 「数据库时间统一存 UTC」「API handler 在 src/api/handlers/」——这些是我需要知道、但不需要被强制拦截的项目知识。

一句判断口诀:如果这条规矩的本意是「拦住一个动作」,它就不该是一句话,而该是一条 settings.json 规则或一个 hook。

示例

改之前(写进 CLAUDE.md,建议性):

# CLAUDE.md
- 绝对不要运行 rm -rf。
- 不要读 .env。
- 推送前先问我。
你:帮我清掉构建产物再重建
我:(把相对路径判断偏了,发出 rm -rf <错误路径>,CLAUDE.md 不拦截)

改之后(拆到 settings.json,强制生效):

{
"permissions": {
"deny": ["Bash(rm -rf:*)", "Read(./.env)"],
"ask": ["Bash(git push:*)"]
}
}
你:帮我清掉构建产物再重建
我:rm -rf 命中 deny —— 这个动作被禁止,我换用更精确的删除方式或请你确认

差别不在我变守规矩了,而在于这次「能不能删」的判断,从我的自由裁量挪到了一道我绕不过去的门锁上。

与《CLAUDE.md 过载》的区别

《CLAUDE.md 过载》讲的是「规则太多、被平均稀释而忽略」——是数量问题;本条讲的是「这条规则压根放错了层」——是位置问题。哪怕你的 CLAUDE.md 只有一行「不要 rm -rf」,它依然拦不住,因为权限本就不归它管。

什么时候例外

「拦动作的归 settings」成立的前提,是这个动作能被一条规则框出来。当框不出来时,留在 CLAUDE.md 不是偷懒,而是唯一可用的控制——但你得知道它只是软约束:

  • 约束是语义的,没有命令 / 路径模式能捕捉。 「别动生产数据」——可生产和预发跑的是同一类命令、同一种 SQL,deny 没有可锚定的字面特征。这种规则写不成锁,只能写成上下文(真正的兜底是换一层:给只读凭据、环境隔离,而不是指望那句话)。
  • 本就是「倾向」而非「红线」。 「优先改配置而不是直接删」这类有方向、但允许我当场判断例外的事,本来就该是建议性的,进 settings.json 反而把合理变通也焊死了。

判据一句话:先问「这个动作能不能用一条 deny/ask 字面框住」——能,就别写成一句话,写成规则或 hook;框不住、或它本就该留给我判断,才留 CLAUDE.md,并清楚它拦不死。

工具差异

Gemini CLI(截至 2026-06):在 Gemini CLI 里这条分界一模一样,只是换了文件名。我的项目记忆文件叫 GEMINI.md(文件名可在 settings 里改),它和 CLAUDE.md 同性质——建议性上下文,我读、我尽量照办,但不是强制层。真要拦住一个动作,归 .gemini/settings.json:它由客户端强制生效,不经过我的判断。所以同样的口诀照搬:该强制的别写进 GEMINI.md,写进 .gemini/settings.json

Codex CLI(截至 2026-06):同样的三层分法,只是文件名不同——AGENTS.md 是建议性上下文(我读、尽量照办,但后一句提示就能盖过它),config.toml 才是强制配置(approval_policysandbox_mode),企业还有一层 requirements.toml 是连用户都改不动的托管策略(可禁掉 approval_policy="never")。AGENTS.md 是跨工具的开放约定,所以「建议≠门锁」这条心法可移植。

Cursor(截至 2026-06):Cursor 把这条分法拆成三件:Rules.cursor/rules/*.mdc + 旧版 .cursorrules)是建议性「指引、非约束」;permissions.json 是最接近 settings.json 的强制层(命令 / 工具 allowlist);sandbox.json 管 OS 隔离。但有个反转值得记:Cursor 官方明说连 permissions.json 的 allowlist / autoRun 也是「尽力而为的便利,不是安全保证」——它的「门锁」比 Claude Code 的 permissions.deny(硬性客户端拦截)更软,该锁死的别只靠它。

GitHub Copilot(截至 2026-06):我这边的建议性上下文是 .github/copilot-instructions.md(外加路径级 .instructions.mdAGENTS.md)——GitHub 明说「Copilot 不保证每次都照做」,它不是权限 / 强制层。所以同样的分界照搬:真要拦住一个动作,别写进指令文件。我的强制层在别处——组织 / 企业的 Copilot policies 与 content exclusions,而不是这些 .md

版本说明

适用版本

CLAUDE.md 是建议性上下文、settings.json 是强制层」是 Claude Code 一贯的设计,全版本适用permissionsallow/ask/deny 三段、hook 生命周期点、env 等具体能力随版本演进,以你所用版本的官方 settings / permissions / memory 文档为准。

延伸阅读与出处