Clipping 微信公众号

CLAUDE.md 不只有两份: 多层加载机制解密和实战技巧

by 问答 原文 ↗
Created: 2026-05-21

公众号名称: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.mdCLAUDE.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.mdtesting.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 #上下文


cover_image

原创 问答 AI 方寸山


内容效果不满意?点此反馈

输入关键词开始搜索