跳转到内容

CLAUDE.md 上下文工程:加载顺序、200 行上限、以及为什么 @import 不省上下文

约 20 分钟 难度:进阶 理解章

三句话决定了 CLAUDE.md 该怎么写:

  1. 它是上下文,不是配置。内容作为系统提示之后的一条用户消息送达,模型会读、通常会遵守,但没有强制保证。要「一定发生」的事情写 hook
  2. 每个会话都全量加载,不管多长。所以每一行都是持续的 token 成本,目标是每个文件 200 行以内。
  3. @import 不省上下文。导入的文件在启动时一样被展开加载。它帮的是组织,不是预算。

按加载顺序,从范围最广到最具体:

作用域位置共享给谁
managed 策略系统目录下的 CLAUDE.md(各平台路径不同)组织全体
用户~/.claude/CLAUDE.md你自己,所有项目
项目./CLAUDE.md./.claude/CLAUDE.md团队,通过版本控制
本地./CLAUDE.local.md你自己,当前项目(记得加 gitignore)

managed 策略位置:

  • macOS:/Library/Application Support/ClaudeCode/CLAUDE.md
  • Linux 和 WSL:/etc/claude-code/CLAUDE.md
  • Windows:C:\Program Files\ClaudeCode\CLAUDE.md

这一层无法被个人设置排除,claudeMdExcludes 也排不掉它。

拼接顺序:为什么「越近的读得越晚」重要

Section titled “拼接顺序:为什么「越近的读得越晚」重要”

Claude Code 从当前工作目录往上走目录树,逐级检查 CLAUDE.mdCLAUDE.local.md

foo/bar/ 里启动会加载 foo/bar/CLAUDE.mdfoo/CLAUDE.md 以及它们旁边的 CLAUDE.local.md。所有找到的文件是拼接进上下文,不是互相覆盖。

顺序是从文件系统根往工作目录方向。所以 foo/CLAUDE.md 出现在 foo/bar/CLAUDE.md 之前——离你启动位置越近的指令,被读得越晚。

每个目录内部,CLAUDE.local.md 追加在 CLAUDE.md 之后。所以你的个人笔记是该层级最后被读到的东西。

工作目录下面的子目录里的 CLAUDE.md 不在启动时加载。它们在 Claude 读取那些子目录里的文件时才被带进来。

monorepo 里被其他团队的 CLAUDE.md 干扰时,用 claudeMdExcludes

{
"claudeMdExcludes": [
"**/monorepo/CLAUDE.md",
"/home/user/monorepo/other-team/.claude/rules/**"
]
}

模式按绝对路径用 glob 语法匹配,各层配置的数组会合并。

块级 HTML 注释在内容注入模型上下文之前会被剥掉:

<!-- 维护者注意:这一节是 2026 Q1 重构后加的,下次架构调整时复查 -->
## 构建命令
...

给人类维护者的备注写在 HTML 注释里,不消耗上下文 token。代码块内部的注释会被保留。

用 Read 工具直接打开 CLAUDE.md 文件时注释仍然可见——剥离只发生在自动注入的路径上。

See @README for project overview and @package.json for available npm commands.
# Additional Instructions
- git workflow @docs/git-instructions.md

关键事实:导入的文件在启动时和引用它的 CLAUDE.md 一起被展开加载进上下文

所以拆成 @import 对上下文预算毫无帮助。它的价值只在文件组织:让一个大文件变成几个小文件,方便维护。

规则细节:

  • 相对路径和绝对路径都可以。相对路径相对包含 import 的那个文件解析,不是相对工作目录。
  • 导入的文件可以递归导入,最大深度 4 跳。
  • 解析会跳过 Markdown 代码跨度和围栏代码块。想在 CLAUDE.md 里提到一个路径而不导入它,用反引号包起来:`@README` 是字面文本,反引号外的 @README 会导入文件。

项目层内存文件里的 import,如果路径解析到你工作目录之外,算作外部导入

Claude Code 第一次遇到项目里的外部导入时会弹一个对话框列出这些文件。拒绝之后导入保持禁用,对话框不再出现。

这个对话框防的是别人提交到共享项目里的文件。用户作用域内存文件里的 import(~/.claude/CLAUDE.md~/.claude/rules/)是你自己写的,不弹对话框。

CLAUDE.local.md 被 gitignore 之后只存在于你创建它的那个 worktree 里。要在多个 worktree 之间共享个人指令,从家目录导入:

# Individual Preferences
- @~/.claude/my-project-instructions.md

Claude Code 读 CLAUDE.md,不读 AGENTS.md

仓库已经在用 AGENTS.md 给别的编码 agent 用的话,建一个 CLAUDE.md 导入它,两边读同一份内容不重复:

@AGENTS.md
## Claude Code
Use plan mode for changes under `src/billing/`.

不需要加 Claude 专属内容的话,符号链接也行:

Terminal window
ln -s AGENTS.md CLAUDE.md

Windows 上创建符号链接需要管理员权限或开发者模式,所以用 @AGENTS.md 导入更省事。

/init 会读 Cursor 规则(.cursor/rules/.cursorrules)和 Copilot 规则(.github/copilot-instructions.md),把相关部分并进生成的 CLAUDE.md。设了 CLAUDE_CODE_NEW_INIT=1 之后它还会读 AGENTS.md.devin/rules/.windsurf/rules/.clinerules

真正能省上下文的:.claude/rules/ 的条件加载

Section titled “真正能省上下文的:.claude/rules/ 的条件加载”

.claude/rules/ 目录下的 markdown 文件,每个文件一个主题:

your-project/
├── .claude/
│ ├── CLAUDE.md
│ └── rules/
│ ├── code-style.md
│ ├── testing.md
│ └── security.md

没有 paths frontmatter 的规则在启动时加载,优先级与 .claude/CLAUDE.md 相同。

paths 的规则只在 Claude 处理匹配文件时才加载——这是它和 @import 的本质区别:

---
paths:
- "src/api/**/*.ts"
---
# API Development Rules
- All API endpoints must include input validation
- Use the standard error response format

触发时机是 Claude 读取匹配的文件,不是每次工具调用。

支持花括号展开:

paths:
- "src/**/*.{ts,tsx}"
- "lib/**/*.ts"

每个花括号组会成倍增加展开后的模式数量:src/*.{ts,tsx} 展开成两个,{a,b}/{c,d}/*.{ts,tsx} 展开成八个。一条规则的整个 paths 列表共享 1000 个展开模式、4 MiB 的预算。超预算的模式会被原样使用,字面花括号匹配不到任何文件。

.claude/rules/ 支持符号链接,可以维护一份共享规则链进多个项目:

Terminal window
ln -s ~/shared-claude-rules .claude/rules/shared
ln -s ~/company-standards/security.md .claude/rules/security.md

循环符号链接会被检测并妥善处理。

用户级规则放 ~/.claude/rules/,在这台机器的每个项目都生效。用户级规则先于项目规则加载,所以项目规则优先级更高。

三个维度:

大小:目标每个 CLAUDE.md 200 行以内。更长的文件消耗更多上下文,并且降低遵守度。

结构:用 markdown 标题和列表分组。Claude 扫描结构的方式和人读一样,组织好的章节比密集段落更容易跟随。

具体性:写具体到可验证的指令。

  • 「用 2 空格缩进」而不是「正确格式化代码」
  • 「提交前跑 npm test」而不是「测试你的改动」
  • 「API handler 放 src/api/handlers/」而不是「保持文件组织」

什么时候该往里加内容——这个判断标准比写法更重要:

  • Claude 第二次犯同一个错
  • code review 抓到一个 Claude 本该知道的项目约定
  • 你把上个会话打过的同一句纠正又打了一遍
  • 一个新同事需要同样的上下文才能开工

什么时候不该加:如果一条内容是多步流程,或者只对代码库的某一部分有意义,它该去 skill 或路径限定的 rule,不该占用每个会话的上下文。

内容类型放哪
每个会话都需要的事实(构建命令、约定、项目布局)CLAUDE.md
只对某些文件有意义的规则.claude/rules/paths
多步流程(PR review、数据库迁移)skill
必须在特定时机发生的动作hook
需要在系统提示层面的指令--append-system-prompt

最后一项要每次调用都传,所以更适合脚本和自动化,不适合交互式使用。

这是一个高频困惑:/compact 之后某条指令好像失效了。

规则是:

  • 项目根的 CLAUDE.md 活过压缩。压缩后 Claude 从磁盘重读并重新注入。
  • 子目录里的嵌套 CLAUDE.md 不会自动重新注入。它们在 Claude 下一次读取那个子目录里的文件时重新加载。

所以压缩后消失的指令,要么只在对话里给过(没写进文件),要么在一个还没重新加载的嵌套 CLAUDE.md 里。

对话里给的临时指令想让它持久,写进 CLAUDE.md。

按顺序:

  1. /context,看 Memory files 下的列表确认文件真的加载了。不在列表里,Claude 就看不到。用 /memory 打开编辑。
  2. 确认这个 CLAUDE.md 在会话会加载的位置上。
  3. 让指令更具体。
  4. 找跨文件的冲突指令。两个文件对同一行为给了不同指导,Claude 可能任意选一个。

调试 path-scoped rules 和子目录里延迟加载的文件,用 InstructionsLoaded hook 记录到底加载了哪些指令文件、什么时候、为什么。这比反复猜快得多。

/doctor 会对已提交的 CLAUDE.md 提出精简建议:它删掉 Claude 能从代码库自行推断的内容(目录布局、依赖列表、架构概览),保留陷阱、理由、以及与工具默认行为不同的约定。这个精简检查需要 v2.1.206 或更高版本。

这个取舍逻辑值得记住:能推断的不写,不能推断的才写。目录结构 Claude 一个 ls 就知道了,写进 CLAUDE.md 是纯浪费;「这个模块看起来该用 A 方案但因为历史原因必须用 B」是它推不出来的,必须写。

自动记忆:Claude 自己写的那部分

Section titled “自动记忆:Claude 自己写的那部分”

除了你写的 CLAUDE.md,还有一套 Claude 自己维护的记忆。

CLAUDE.md自动记忆
谁写Claude
内容指令和规则学到的东西和模式
作用域项目 / 用户 / 组织每仓库,worktree 之间共享
加载每个会话全量每个会话前 200 行或 25KB

存储位置是 ~/.claude/projects/<project>/memory/<project> 从 git 仓库派生,所以同一个仓库的所有 worktree 和子目录共享一个目录。

目录结构:

~/.claude/projects/<project>/memory/
├── MEMORY.md # 简明索引,每个会话加载
├── debugging.md # 详细笔记
└── api-conventions.md

MEMORY.md 只有前 200 行或前 25KB(先到者为准)在每个会话开始时加载,超出部分不加载。所以它被设计成一个索引,详细笔记放在单独的主题文件里,Claude 需要时用文件工具按需读。

Claude 写完 MEMORY.md 后 Claude Code 会量一次:接近上限时提醒 Claude 精简,超过上限时写入仍然成功但返回一个错误告诉 Claude 重写索引——因为超出部分在下次加载时会被丢掉。

量的是会加载的内容:YAML frontmatter 和块级 HTML 注释在加载前被剥掉,不计入上限。

这个上限只对 MEMORY.md 生效。CLAUDE.md 无论多长都全量加载,只是越短遵守度越好。

自动记忆是机器本地的,不跨机器同步。主对话的自动记忆不会加载进 subagent(fork 是例外,它继承父对话)。

关掉:

{ "autoMemoryEnabled": false }

或环境变量 CLAUDE_CODE_DISABLE_AUTO_MEMORY=1。用 /memory 可以浏览 Claude 存了什么——都是纯 markdown,随时可以编辑或删除。

  • /context 看 CLAUDE.md 在上下文里占多少。如果占比让你意外,用 /doctor 的精简建议砍一轮。
  • 把 CLAUDE.md 里最长的那个「流程」章节搬成 skill,再跑 /context 对比。这个对比能让「200 行」这个建议从数字变成体感。
  • 给一个只对某个目录有意义的规则加 paths frontmatter,然后分别在匹配和不匹配的文件上工作,用 InstructionsLoaded hook 确认它真的只在该加载时加载。
  • claudeMdExcludes 对嵌套 rules 目录的匹配细节。文档给了示例,但「排除一个目录」和「排除目录下的具体文件」在行为上是否有区别没有核实。
  • 自动记忆的写入判定。文档说 Claude 根据「这条信息在未来对话里是否有用」决定要不要存,具体的判定过程不可观测。
  • /doctor 精简建议的具体判定规则。文档描述了它保留什么删什么,但没有给出可预测的规则。
  • 压缩后重新注入的具体时机。「项目根 CLAUDE.md 活过压缩」确认了,但重新注入是紧接压缩之后还是下一轮请求时,未见说明。
这一节有错或讲不清? 提个 Issue 直接改文档 请作者催更