为什么你的 CLAUDE.md 不起作用:从 README 升级为规约的四个动作
很多团队把 CLAUDE.md 写得很认真:技术栈、目录约定、测试要求、一两个偏好,洋洋洒洒几百行。然后实际跑下来发现一个尴尬的现象——Claude 礼貌地"读"完,依旧按训练数据里那套最常见的写法继续干活。
文件在那儿,但没起作用。
这不是写得不够多的问题,是类别错了。README 是"项目介绍",给人看;CLAUDE.md 是"行为规约",给 Agent 用来约束自己。把规约写成介绍,Agent 不会反驳你,它只是默默回到最大概率分布。
下面把这件事拆成四个能马上落地的动作,并附一个可以接入 CI 的最小校验器示例项目。
动作一:放弃"一个根文件搞定一切"
Claude Code 不只是读项目根目录的 CLAUDE.md。它在哪个子目录工作,就会顺着目录链把沿途的 CLAUDE.md 一起加载,子目录的规则会覆盖父目录。
这件事很关键,因为上下文是有方向性的。写一个数据库迁移和写一个后台任务,需要遵守的规则完全不一样。塞进同一个根文件,要么互相重复,要么互相矛盾,最后 Agent 自己挑一条用。
一种能跑得起来的结构(用更通用的 Python 服务做例子):
project/
├── CLAUDE.md # 通用约束、技术栈、沟通协议
├── app/
│ ├── models/CLAUDE.md # 模型层规则:迁移、索引、查询约束
│ ├── tasks/CLAUDE.md # 异步任务规则:幂等、重试、失败处理
│ └── services/CLAUDE.md # 服务对象:何时拆、接口契约、禁止 God Object
└── migrations/CLAUDE.md # 迁移规则:必有 down、必有索引方案
每个文件只回答一件事:"在这个目录里,什么样的代码算合格"。根文件最后写——你把每个目录文件写完,自然就看出哪些规则是反复出现的、值得提到根目录。
如果你正在改一个已经堆得很厚的根 CLAUDE.md,先做一件事:把规则按"哪个目录会用到"打标签。打不上标签的,往往就是写得太抽象、Agent 根本无从执行的口号。
动作二:把"事实"和"规则"分开放
任何一个 CLAUDE.md 里,至少有两种内容,离 Agent 的决策距离完全不同。
事实(Facts)描述环境,写错了 Agent 会调用错版本:
## 技术栈
- Python 3.11
- FastAPI 0.115
- PostgreSQL 15
- Celery 5.4
- pgvector 0.7(仅 app/models/document.py 在用,未在其他地方部署)
规则(Rules)约束行为,违反了会有故障:
## 数据库
不要在循环里 update 记录。要用批量更新。
不要在没有 WHERE 的情况下调用 update 批量接口。
对 documents 表的任何查询,合并前必须先 EXPLAIN ANALYZE。
工程师习惯把这两类混在段落里写。Agent 读到的时候,给"事实"和"规则"的权重差不多,结果是两边都被淡化。用显式标题分开。事实归一节、规则归一节,Agent 在做决策时只查规则,在写代码时只查事实。
这一条听起来像废话,但凡是把 CLAUDE.md 写到 800 行以上的项目,绝大多数都没分开。
动作三:用"禁止句"代替"建议句",并附上原因
这是这套写法里最重要的一条。
"优先使用批量更新"会输给"绝不要在循环里 update 记录"。两句话表达的意图一样,但 Agent 处理它们的方式完全不同——前者是偏好,要和其他偏好做权衡;后者是约束,直接砍掉一个分支。
特别是在写到一半、Agent 想尽快收尾的时候,约束比偏好可靠得多。
一个实用的自检方式:把 CLAUDE.md 里所有"prefer X / 推荐 X"全部尝试改写成"never Y / 不要 Y"。改不出来的,说明这条规则本身就太虚,不具备执行价值。
对照看一组:
# 弱
推荐使用符合框架习惯的写法。
# 有用
不要在没有写明业务理由的注释前提下,抽出新的 service object。
不要 rescue 一个裸 Exception 而不记录异常和调用上下文。
不要写一个 perform 用相同参数调两次就会出错的后台任务。
第一条 Agent 已经知道了。后三条是关于你这个代码库的特有约束,写成"不许"它没法争。
写完禁止句,再做一件事——把"为什么"加上:
# 没有 why
不要在没有 WHERE 条件下调用 update_all。
# 有 why
不要在没有 WHERE 条件下调用 update_all。
2024 年 11 月这个仓库出过两次生产事故,原因是后台任务持有了过期引用,
直接全量更新了 users 表。任何 update_all 都必须先约束范围。
两条规则在原样场景下行为一致。区别在于:遇到没见过的相似场景时,第二条能让 Agent 自己推断风险。它会想"这个新场景是不是也有同样的失败模式",并给出正确答案。
把每条非显然的规则写成下面这个格式,效果最稳:
**Rule:** [约束]
**Why:** [触发这条规则的故障模式或历史事故]
**Exception:** [如果存在例外,写在这里]
不是每条规则都要套这个模板,但每条不显然的规则都要有原因。没原因的规则,Agent 只会照着字面执行,遇到边界情况就失灵。
动作四:让 Agent 反过来帮你维护这份文档
这一步绝大多数团队跳过了,导致 CLAUDE.md 永远停留在第一周写完的版本上。
每次结束一段较长的会话——特别是你纠正过它好几次的那种——加一段收尾提示:
最后一件事:本次会话里你学到了什么应该写进 CLAUDE.md?
列具体的规则或事实,不要泛化。按文件里已有的格式起草。
会话结尾时,Agent 对"哪些规则被反复查询、哪些规则它解读得很犹豫、哪个决定它自己其实没把握"是有信息的,比你记得清楚。让它把这些显式说出来,你筛掉过于一般化的,把具体、准确的合并进文件。
配套地,把 CLAUDE.md 真正当成 schema 一样维护:
- 进 Git,每条规则改动的 commit message 写清"因为发生了什么才改"。
- 每周固定 10 分钟过一遍,问三个问题:
- 这周 Agent 哪条规则执行错了?改写它。
- 这周做了什么决定还没沉淀进规则?补上。
- 哪条规则已经因为代码库改变而过时?删掉或更新。
会话开头如果是改一段关键代码,先让 Agent 自查一次:
请先读 CLAUDE.md,再读你即将修改的文件。在写任何代码之前,列出:
1. 这个任务命中了 CLAUDE.md 里哪些规则
2. 目标文件里已经存在哪些违反这些规则的代码
3. 哪些规则在本任务中存在歧义,需要我先确认
三十秒,能把两类问题提前暴露:Agent 会前后不一致执行的规则、目标文件里已存在的违规代码(不处理它就会"按周围风格"被继承下去)。
把规约变成可机器校验
四个动作做完,文件还是会随着时间漂移。最低成本的兜底是写一段 lint,把"是否分了事实与规则、是否有 Never 句、是否带 Why、是否有示例"做成机器检查。这样新人提 PR 改了 CLAUDE.md,CI 会直接挡掉那种又退回 README 风格的版本。
配套示例项目提供了一个 60 行级别的 Python 实现,跑起来很简单:
cd examples/claude-md-lint
python -m src.cli ../../ # 扫描整个仓库下的所有 CLAUDE.md
python -m src.cli --json . # 输出 JSON,给 CI 用
python -m unittest discover tests
它做四项检查:
- 文件里是否同时有"事实型"小节(如技术栈/Stack/版本)与"规则型"小节(含 Never/不要/必须 等关键词)。
- 非显然规则是否带"原因/Why/因为/事故"等解释。
- 是否至少有一段代码块作为示例。
- 是否包含"反例/正例"或"Bad/Good"对照。
输出长这样:
project/CLAUDE.md
[PASS] facts-section
[PASS] negative-rules
[WARN] rules-have-reasons 2/5 条规则缺少原因
[PASS] inline-examples
[FAIL] bad-good-contrast 未找到反例/正例对照
把它接进 CI 之后,CLAUDE.md 就和 schema 一样被持续校验,不会悄悄退化成 README。
什么时候不需要这么写
不是每个项目都值得拆四个动作。下面几种情形保持简单就行:
- 仓库本身就是 demo 或一次性脚本,几十个文件以内、生命周期不超过一个月。
- 团队没有 Claude Code 日常使用习惯,
CLAUDE.md改一次三个月没人看;这时优先做的是把使用习惯跑通,再谈规约。 - 规则数量少到一页屏幕能放下;这时分目录文件反而增加心智负担。
只要项目还在长——人多起来、目录分层加深、Agent 写的代码开始进生产——这套写法的收益就会很快超过维护成本。
结尾:把它当成你交接给下一个自己的契约
CLAUDE.md 真正服务的对象,不是 Agent,是你下一周、下一个月、下一个新人面对这个代码库的那个状态。
每次开会话,相当于把整个仓库交给一个没见过它的人,他能力很强、读过所有公开代码、默认写最常见那种实现——除非你提前告诉他不许、为什么不许、什么时候例外。
写规则。写为什么。写反例。每周花 10 分钟回看。让 Agent 帮你把规则更新进文件。然后用 lint 卡住退化的版本。
这套东西做下来,CLAUDE.md 才会从"工程师的诚意"变成"代码库真正学到的东西"。
原文参考:How CLAUDE.md actually works — Wilbur Suero