CLAUDE.md 在 /memory 中可见、文件确实加载,但 Claude 仍然不遵守其中某条规则——排在首位的根因往往不是”模型叛逆”,而是该指令在加载链里被相对优先级更高的同主题指令覆盖,或因描述过于模糊而无法被验证遵守。CLAUDE.md 本质上是一段以用户消息形式追加到上下文末尾的文本,模型没有硬保证,因此一切降低识别与可验证性的写作特征(嵌套条件、模糊动词、未声明 scope/priority、单文件超过两百行、跨文件主题重复)都会显著拉低遵循率。
一、把 CLAUDE.md 当作”软约束”是理解前提
官方规范把 CLAUDE.md 描述为”会话开始时读取的持久指令”,以用户消息的形式注入到上下文末尾。理解这一点能解释大多数”我写了规则但没生效”的反馈——它不是配置项,也不是钩子;模型读到它、尝试遵守,但没有强制执行的回环。
当 /context 列表里能看到文件、运行 /memory 也能列出它,但某条规则依旧被忽略时,问题的根因通常落在三个方向:
- 优先级竞争:另一份同主题指令在加载链更靠后的位置覆盖了它;
- 可验证性不足:规则写得太空,模型无法判断自己是否遵守,干脆按当前上下文最贴合的写法走;
- 上下文位置:规则落在长文件的中后段,遭遇了”lost in the middle”效应,被模型注意力稀释。
把这三个方向各拆出若干典型写作特征,遵循率就会从”看心情”变成”几乎稳定”。
二、排查流程:从成本更低的检查开始
判断某条指令为何失效,按以下顺序逐步定位,避免一上来就重写整份文件。
- 运行
/memory,确认目标文件确实出现在加载列表中; - 用关键词在所有已加载文件里 grep 同主题指令,查找是否被覆盖;
- 检查文件总行数,单文件超过两百行就把中后段规则迁移到
.claude/rules/并用 paths frontmatter 限制触发范围; - 给候选规则补上 priority 关键字(CRITICAL / MUST / SHOULD),让其在新位置醒目;
- 把每条”自然语言偏好”改写为可验证的硬约束(”函数不超过 40 行””不要使用默认导出”);
- 如果指令必须严格在固定时机执行(提交前自动跑测试、每次编辑后格式化),改写为 Hook,不要继续留在 CLAUDE.md。
这套流程能在两三分钟内把”模型不听话”从玄学问题降级成可观察的工程问题。
三、写作特征对遵循率的影响
下表把常见写作问题与官方文档中给出的纠偏条目对齐。每条都对应一个具体症状,方便按表对照修改。
| 写作特征 | 触发症状 | 官方对应纠偏条目 |
|---|---|---|
| 模糊动词(”好好写””规范处理””尽量避免”) | 同样输入每次行为不同 | 改为可验证的硬约束(变量命名、函数行数、错误返回类型) |
| 嵌套条件(”如果是 A 且不是 B 但又要考虑 C”) | 规则边缘条件被忽略 | 拆为多条独立规则,或迁移到 path-scoped rule |
| 未声明 scope(不知道该放用户级还是项目级) | 同名规则互相打架 | 显式声明放在 ~/.claude/CLAUDE.md 还是 ./CLAUDE.md |
| 未声明 priority(与其他文件重复同主题) | 加载顺序竞争时随机胜出 | 加 CRITICAL / MUST / SHOULD 标记,或消除重复 |
| 规则位置靠后(长文件末段) | lost in the middle 效应 | 控制单文件 ≤ 200 行,长内容拆到 .claude/rules/ |
| 重复主题(多文件都讲同一件事) | 跨文件歧义 | 一事一地,删除次要副本 |
| 期望”时机触发”的规则(提交前跑测试) | 在用户催促时被绕过 | 改写为 Hook,绕开自然语言优先级 |
排查方法上,/memory 是成本更低的探针——文件不在列表说明没加载;文件在列表但规则没生效,再进入上表的对照分析。
四、可验证约束的写法
把”好好写代码”这种主观期望转成可被模型逐次校验的硬规则,是提升遵循率更直接的手段。下面以 TypeScript 服务层规则为例,对比改造前后的可验证性。
# 改造前(不可验证)
- 服务层代码要规范
- 错误处理要合理
- 命名要符合团队风格
# 改造后(可验证)
- 服务层函数不超过 40 行
- 错误一律返回 Result<T, E>,不抛异常
- 导出只允许命名导出,禁止 default export
- 命名:变量 camelCase,类 PascalCase,常量 UPPER_SNAKE
前者每次执行都可能给出不同输出,因为”规范””合理””符合风格”都没有客观判定标准;后者四条都是模型可以在心里打勾的硬约束,几乎不会”看心情”。
路径级规则(path-scoped rule)把”只在某个目录生效”的指令从 CLAUDE.md 主文件里抽出来,用 frontmatter 限定触发条件。
# .claude/rules/api-endpoints.md
---
paths:
- "src/api/**/*.ts"
---
# API 路由规范
- 所有端点必须校验入参,使用 zod schema
- 错误响应统一遵循 RFC 7807 Problem Details 格式
- 每个端点必须带 OpenAPI 注解
这种写法既避免主文件膨胀,又让”只在后端路由目录生效”的指令不会污染前端组件生成,scope 也就清晰了。
五、优先级声明的最小公约数
当规则不得不与其他文件的主题发生重叠时,priority 关键字是成本更低的护城河。社区里被反复验证的写法是四档分级,每档对应明确的”可违反边界”。
- CRITICAL:违反会造成生产事故或安全风险,必须执行;
- MUST:默认遵守,遇到明确冲突才让步;
- SHOULD:默认遵守,遇到冲突可被覆盖;
- NICE TO HAVE:上下文宽裕时执行,紧张时放弃。
# 团队规范
- CRITICAL: 涉及生产数据库的删除语句必须先 dry-run 预览
- MUST: 提交前运行 `pnpm test`,失败则禁止 commit
- SHOULD: 新增模块附带 README 段落
- NICE TO HAVE: 函数命名尽量带动词
CRITICAL 这一档的存在相当于在长文件里给模型”打了个灯”,即便规则被埋在 150 行之后也不会被 lost in the middle 吞掉。
六、CLAUDE.md 失效的延伸:什么时候该改用 Hook
Hook 提供”无论模型怎么决定都会执行”的强制点,恰好补足 CLAUDE.md 的软约束短板。如果你的指令描述的是”某个时机必须发生的事”,就应当迁移到 Hook。
- 提交前自动跑测试与 lint,对应
PreToolUseHook; - 每次编辑后自动格式化,对应
PostToolUseHook; - 危险命令(
rm -rf、git push --force)拦截,对应PreToolUseHook; - 加载指令文件时记录到日志,对应
InstructionsLoadedHook(用于排障)。
把这类”时机触发”需求从 CLAUDE.md 迁出后,主文件就只剩下”模型需要长期内化”的偏好,遵循率反而回升。
常见问题(FAQ)
Q1:怎么确认 CLAUDE.md 真的被加载了?
在 Claude Code 里运行 /memory,加载列表里没有它就说明没生效。
Q2:单文件多长算”过长”?
社区共识是两百行左右,超过后中段规则容易失守,建议拆到 .claude/rules/。
Q3:CRITICAL 和 MUST 真的有用吗?
有用,priority 关键字相当于在长文件里给规则打灯,能缓解 lost in the middle。