CLAUDE.md 多层加载:比你想的复杂
事情的起点是一份飘忽不定的报告。
我们内部有个日志分析 Agent,输出格式是 HTML,这个写在那个模块的 CLAUDE.md 里,白纸黑字。结果某天开始变得不稳定:同样的指令,有时候出 HTML,有时候出 Markdown,跑三次三种结果。
第一反应是 prompt 问题。把那个模块的 CLAUDE.md 改到几乎只剩一句「输出格式必须是 HTML」,还是抽奖。查用户级的 ~/.claude/CLAUDE.md,没有相关规则。查子目录,没有。
折腾了两个小时,才想起来:上周我在父目录 aiworking/ 建了一份 CLAUDE.md,用来给底下几个子模块做统筹,里面有一行「分析类报告默认 Markdown」。
目录结构是这样的:
aiworking/
CLAUDE.md ← 写着「分析类报告默认 Markdown」
loganalysis/
CLAUDE.md ← 我在这里启动 Agent,写着「输出 HTML」
我以为只有 loganalysis/CLAUDE.md 在生效,但实际上两份都进了上下文,Claude 在两条冲突规则之间,看心情选一个。
加载机制是什么
搞清楚这件事之后,我仔细查了 Claude Code 的文档,把加载逻辑整理了一下。
向上遍历:从你启动 Claude 的目录出发,一级一级往上走,每一级有 CLAUDE.md 就纳入,一直到磁盘根目录为止。loganalysis/ 目录下启动,实际加载路径是:
/root/...(各级祖先目录,一般没有)
~/.claude/CLAUDE.md ← 用户级,固定加载
aiworking/CLAUDE.md ← 父目录,向上遍历命中
loganalysis/CLAUDE.md ← 当前目录
loganalysis/CLAUDE.local.md ← 本地覆盖(如果有)
拼接不覆盖:找到的所有文件不是「近的覆盖远的」,而是全部拼在一起注入上下文,从根到叶顺序排列。所以两条冲突规则同时存在,距离近的排在后面,但不是必然胜出——这取决于 Claude 怎么处理上下文里的矛盾,不保证严格遵守。
子目录懒加载:这个知道的人更少。启动工作目录下的子目录里如果有 CLAUDE.md,启动时不会加载。要等到 Agent 在会话中真正访问那个子目录里的文件时,该 CLAUDE.md 才会被动态挂载进来。整个会话没有访问那个子目录,它的 CLAUDE.md 不进上下文。
还有一点:CLAUDE.md 不在系统提示词里,它是作为一条用户消息追加在系统提示后面的。这解释了为什么它「不保证严格遵守」——它不是强制配置,Claude 会尽量照做,但遇到矛盾时怎么处理有随机性。
踩坑之后我改了什么
首先把父目录那条「分析类报告默认 Markdown」删掉,改成了更精确的指向:
<!-- aiworking/CLAUDE.md -->
适用范围:本目录下各子模块的通用规范
### 代码规范
- Go 1.22+,禁止使用 init()
- 错误处理用 pkg/errors 包装,禁止裸 fmt.Errorf
### 注意
各子模块的输出格式以各自的 CLAUDE.md 为准,本文件不约定格式
加了最后那句,明确把格式决策权交给子目录,避免父级规则模糊覆盖。
第二个改动是控制规则密度。Augment Code 团队做过一个实验,从一个 monorepo 里提取了几十份 AGENTS.md 逐一测试,发现最好的 AGENTS.md 带来的提升相当于把模型从 Haiku 换成 Opus,而最差的比完全不写还糟。坑在于:同一份文件在常规任务上把质量提升了 25%,在复杂任务上让完整性下降了 30%——写太多规则,规则之间会干扰,Agent 开始在「中间迷失」。
现在我的主文件控制在 100 行以内,只写高频场景的规范;细节推到按需加载的子文件里,在主文件里只写「需要时参考 @docs/api-rules.md」这样的入口。
第三个改动是清理注释。CLAUDE.md 里的块级 HTML 注释(<!-- 备注 -->)在注入上下文前会被剥掉,不占 token。所以现在我在文件里保留了大量维护注释,说明每条规则的来源和生效日期,这些注释只给写文件的人看,不会进 Claude 的上下文:
<!--
2026-06-15 添加:排查完 loganalysis 输出格式冲突问题后
明确父目录不约定格式,避免向下覆盖子模块规则
-->
各子模块的输出格式以各自的 CLAUDE.md 为准
AGENTS.md vs CLAUDE.md
顺带提一下这两个文件的区别,网上说法很乱。
CLAUDE.md 是 Claude Code 专用的,只有 Claude Code 会去读它。AGENTS.md 是一个更通用的约定,OpenAI Codex、Cursor、一些开源 Agent 框架都在尝试认这个文件名。
如果你用的是 Claude Code,两个都认,CLAUDE.md 优先。如果你想同时兼容多个工具,用 AGENTS.md。如果只用 Claude Code,CLAUDE.md 就够了,不用两份都维护。
有一个我自己的做法:在项目根目录建一份 AGENTS.md,内容是通用规范;然后建一个 CLAUDE.md,内容只有一行:
@AGENTS.md
这样 Claude Code 读到 CLAUDE.md,拉取 AGENTS.md 的内容,其他工具直接读 AGENTS.md,一份规范两边都能用。这个 symlink 式的做法不是官方推荐的,属于民间发现,用了几个月没出问题。
一句话总结
CLAUDE.md 的加载是「全量拼接,不是近者优先」。如果你在大型 monorepo 里或者多级目录结构里用 Agent,需要主动管理每一级 CLAUDE.md 的作用域,并且在父级规则里明确标注「以子目录为准」的边界,否则冲突规则会让 Agent 的行为变得不可预测,排查起来很费劲。