CLAUDE.md 不只有两份: 多层加载机制解密和实战技巧
公众号名称:AI 方寸山
作者名称:问答
发布时间:2026-05-21 09:18
我以为 CLAUDE.md 只有两种:用户级一份,项目级一份(包含子目录的)。
直到最近,我让 Claude 出一份 HTML 报告。结果它给我了 Markdown。重跑,HTML。又跑,Markdown。就像薛定谔的猫。我把项目目录里那份 CLAUDE.md 翻了三遍,规则白纸黑字写的是 HTML,问题不在这份文件里。
一份飘忽不定的报告
我手上有几份代码仓库,规模上亿行、上万个子模块。我在重点维护的几个子模块各自挂了 CLAUDE.md,分别定制规则。前几天天我在一个叫 aiworking/loganalysis 的目录里启动 Claude,让它跑一份日志分析报告,要求输出 HTML。结果它给我 Markdown。再跑又是 HTML。第三遍又翻车。
我先怀疑自己 prompt 写得不够明确,把 loganalysis/CLAUDE.md 删到几乎只剩一句「输出 HTML」。还是抽奖。
查 ~/.claude/CLAUDE.md,没相关规则。查 README,没有。查子目录 .claude/ 配置,没有。
直到我想起来,两周前我在 aiworking/ 这个父目录也写了一份 CLAUDE.md。当时是要给底下几个子模块做路由级的统筹,里面有一行:「分析类报告默认 Markdown」。
目录长这样:
aiworking/
CLAUDE.md ← 这里写了"日志分析默认 Markdown"
loganalysis/
CLAUDE.md ← 我在这里启动 Claude,要求 HTML
我以为只有 loganalysis/CLAUDE.md 在生效。结果两份一起生效,Claude 在它们之间随机挑一份。
CLAUDE.md 是怎么被找到的
Claude Code 在会话启动时找 CLAUDE.md,有三条主线。
第一条是,向上遍历。从你当前所在目录出发,沿目录树一级一级往上走,每经过一层就检查这一层有没有 CLAUDE.md 和 CLAUDE.local.md,有就纳入。一直走到磁盘根目录。
我遇到的是, 从 aiworking/loganalysis 启动,查找路径是这样:先看 loganalysis/,命中 loganalysis/CLAUDE.md。上一级到 aiworking/,命中 aiworking/CLAUDE.md。再往上继续找,到根目录为止。两层目录的 CLAUDE.md,启动时一起进入了上下文。
第二条是,固定位置。这个大家耳熟能详了,就是用户级 ~/.claude/CLAUDE.md 和组织级(内置在CC中,一般遇不到)不靠遍历,启动时直接拉进来。本地级 CLAUDE.local.md 和项目级是同一套路径上的两个文件,本地级一般放项目根,应加进 .gitignore。
第三条是,子目录下的动态加载。启动的工作目录下的子目录里如果也有 CLAUDE.md,规则反过来了。启动时不查。要等到 Claude 在会话过程中真的去读那个子目录里的某个文件时,CLAUDR.md 才会被临时挂载进来。如果整个会话没有访问那个子目录,它的 CLAUDE.md 是不会加载在上下文里的。
当然,现在说的 CLAUDE.md 也包含 CLAUDE.local.md,这两者的区别只是要不要同步给别人。
经过这3条路线,CC 会把四种 CLAUDE.md 都找到,从最广到最窄是:组织级 → 用户级 → 项目级 → 本地级。
找到之后怎么加载
我原本以为多份 CLAUDE.md 之间是「覆盖」关系:离得近的赢,远的被盖。因为很多配置系统就是这么设计的。
但是,CLAUDE.md 走的是另一套:CC会把找到的 CLAUDE.md 全都进上下文,谁也不替换谁。
然后就是拼接这些多个 CLAUDE.md,而且按路径顺序的:就是根据找到的CLAUDE.md 从磁盘根目录方向向下读取拼接,你当前工作目录的是排最后边。同一级里,CLAUDE.local.md 紧跟在 CLAUDE.md 后面。
我那个例子,加载顺序拼出来大致是:
靠前 组织级 CLAUDE.md
用户级 ~/.claude/CLAUDE.md
aiworking/CLAUDE.md
loganalysis/CLAUDE.md
靠后 loganalysis/CLAUDE.local.md
另外,有个点也得提下,CLAUDE.md 的不在系统提示词里。它是作为一条用户消息追加在系统提示之后送进上下文的,更像是「会话开始时塞给 Claude 的一段话」。
这就解释了为什么它「不保证严格遵守」。它不是强制配置,Claude 会读、会尽量照做,但你不能指望它像 hooks 那样硬性触发。
还有两个相关的小坑。
CLAUDE.md 里 block 级的 HTML 注释()在注入上下文前会被剥掉。这是官方机制,可以拿来放维护者备注,不花 token。还是很方便的,可以写人看的注释。
另外,/compact 执行后的行为也要提下。项目根 CLAUDE.md 会被自动重读、重新注入。子目录里那些懒加载文件不会自动重新注入,得等 Claude 再次读到那个子目录的文件才会重新挂上。如果一条关键规则只写在子目录的 CLAUDE.md 里,压缩之后可能就「丢」了。
拼起来之后,规则打架怎么办
这是我那个 HTML/Markdown bug 的核心问题。aiworking/CLAUDE.md 说 Markdown,loganalysis/CLAUDE.md 说 HTML。两条都被原样拼进了上下文。CC 不会在启动时检查它们矛不矛盾,也不会停下来问我要哪个。
它会:随机挑一个。
这给我提了个醒,当在当前目录启动 CC 时,最好检查下其路径上每一个文件夹下是否有 CLAUDE.md,然后确认是否有冲突。最方便的指令是通过 /memory 查看。
CLAUDE.md 之外:rules 和 @import
当我的电脑中工作内容和维度多起来以后,就会发现,CLAUDE.md 也满足不了。比如,我有两套完全无关的内容都要写进 ~/.claude/CLAUDE.md,因为这两套规则我会在很多目录下使用,我不想每个目录拷贝一份,那怎么办?两个办法。
先说 .claude/rules/。可以把规则按主题拆成多个文件,比如 code-style.md、testing.md,统一放在项目的 .claude/rules/ 下:
your-project/
├── .claude/
│ ├── CLAUDE.md
│ └── rules/
│ ├── code-style.md
│ ├── testing.md
│ └── log-analysis.md
默认情况下,rules 里的每个 .md 文件都在启动时加载,优先级和 .claude/CLAUDE.md 同级。
真正要用的是 paths 字段。在 rules 文件开头加一段 YAML frontmatter,可以让这条规则只对匹配的文件生效:
---
paths:
- "**/*.log"
- "logs/**/*"
---
# 日志分析规则
- 先 grep 关键字降噪,不要逐行读
- 默认输出 HTML 报告
- 时间区间用 ISO 8601 格式
当 Claude 读到匹配这些 path 的文件时,这条规则才被加载进上下文。没匹配的会话里,这条规则不占空间。
我自己的工作流里,编写代码和分析日志这两件事的规则完全不一样。写代码要先探索、后讨论、再下手。分析日志要先降噪、后聚焦,绝不逐行读。
把这俩硬塞在同一份 CLAUDE.md 里,每次会话两套规则都在等待,互相干扰。挪到 ~/.claude/rules/ 里分两个文件,各自配 paths 之后,工作流瞬间不打架,干哪种活就只有那种规则上场。
~/.claude/rules/ 是用户级,跨项目生效。项目专属规则放 .claude/rules/。两者都加载时,用户级在前,项目级在后,所以项目级优先级更高。
另一个办法是 @import。在 CLAUDE.md 里写一行 @docs/git-rules.md,那个文件的内容会在加载时被展开、拼进上下文。
几个细节要唠叨下。路径相对、绝对都行,但相对路径相对的是「写这行 import 的那个文件」,不是你的工作目录。可以嵌套:被 import 进来的文件里还能再 import,最多 5 层。被导入的内容一样占上下文,拆成 import 是为了好维护,省不了 token。
rules 和 import 不冲突,目标也不一样。rules 是「按路径条件加载」,import 是「展开拼接」。跨项目的、有触发条件的规则放 ~/.claude/rules/。某份说明文档只是想引用一次但不想复制,用 @import 更省事。
排查技巧
机制看完,再分享几个排查技巧,基本能解八成上下文问题了。
/memory 。它会列出当前会话已经加载的所有 CLAUDE.md、CLAUDE.local.md 和 rules 文件。某份文件没出现在列表里,说明它没被加载。列表里冒出你不认识的文件,那很可能就是干扰源。我那个 HTML/Markdown bug 就是通过这个排查出来的,让冲突当场现形。
claudeMdExcludes。如果某个上层目录的 CLAUDE.md 干扰你了,但又不方便去改它(比如上级目录确实要如此用),就用 claudeMdExcludes把它从 .claude/settings.local.json配置里排除:
{
"claudeMdExcludes":[
"/Users/you/aiworking/CLAUDE.md",
"**/legacy/**/CLAUDE.md"
]
}
数组里每一项是绝对路径或 glob 通配。写在 settings.local.json 里不进版本控制,只对你这台机器生效。
InstructionsLoaded 。这是 hook 中的配置,可以查看哪些加载进了上下文。前两个看的是「此刻加载了什么」,hook 记录的是「每一次加载是怎么发生的」,适合排查那种时灵时不灵、跟子目录懒加载有关的疑难。每当有 CLAUDE.md 或 rules 文件被加载,它就触发一次,把加载原因和文件路径写到日志里。平时不用上。规则时灵时不灵、/memory 也看不出问题的时候再开它,对着日志找时间点。
最后
做个小结,虽然不是啥大发现,但是提供工具的使用技巧还是很有用的:
-
规则不生效就先敲
/memory,看清楚 Claude 这次到底加载了哪些文件。答案常常不在你正盯着的那份文件里,而在你都忘了存在的上层目录里。 -
规则放对层。跨项目都成立的个人偏好放
~/.claude/CLAUDE.md,项目专属规则放./CLAUDE.md,私人不进 git 的写CLAUDE.local.md。 -
冲突回到源头改。不要靠「在更深的目录里再写一条相反的」去压住,拼接顺序不保证靠后的赢,冲突的根在哪一层就改哪一层,删一份比加一份稳。
-
CLAUDE.md 长了别硬拼。如果你和我一样有两套以上的工作流(写代码 / 分析日志 / 文档维护)混在一个目录里,按
paths字段把规则拆到.claude/rules/是更稳的做法。只有干那种活、读到那种文件时,对应的规则才被拉进上下文,平时不占空间。 -
被上一层级的 CLAUDE.md 干扰、又不方便改它的时候,
claudeMdExcludes是局部解药。
CLAUDE.md 我以前当配置文件用,现在理解了加载机制之后,它在我眼里变成了「会话开始时给 Claude 念的一段话」。我念什么、念几份、Claude 怎么听,比我以为的复杂得多。
感谢您看到这里,希望对你有用。
#CLAUDE.md #Claude #ClaudeCode #上下文

原创 问答 AI 方寸山
内容效果不满意?点此反馈