深入浅出 Claude Code(一):从源码理解 CLAUDE.md,重写你的配置
- 内容介绍
- 文章标签
- 相关推荐
新一期来啦:
深入浅出 Claude Code(二):从源码解构 MEMORY.md,重塑你的上下文注入链路 开发调优上一篇我们重写了 CLAUDE.md,但这只是表层。今天这篇,我们将顺着代码的调用栈,扒开 Claude Code 的记忆系统,看看你的指令在底层究竟被赋予了什么级别的权重。 你让 Claude Code "记住"的东西,下次对话它可能完全不记得。或者记得,但不照做。 其实不是功能坏了——是你不清楚这些 memory 在系统里是怎么流动的。存在哪、怎么加载、权重多高、什么格式才有效,搞清楚这条…
大多数人写 CLAUDE.md 的方式是:随便写几条规则,感觉效果不明显,然后放弃。
你不知道这些文字在 Claude 眼里长什么样、被放在哪里、和其他指令的优先级关系是什么。
今天我来带各位佬友们剖析一下 claudecode 的底层逻辑
全网统一小恐龙头像,在别的地方看到的家人们点点赞
1. CLAUDE.md 不在 System Prompt 里
这是最大的认知误区。
大多数人以为 CLAUDE.md 的内容被拼接到 system prompt 中。不是的。
源码中,CLAUDE.md 通过 prependUserContext() 函数注入(utils/api.ts:449-474):
export function prependUserContext(messages, context): Message[] {
return [
createUserMessage({
content: `<system-reminder>
As you answer the user's questions, you can use the following context:
# claudeMd
${claudeMdContent}
IMPORTANT: this context may or may not be relevant to your tasks.
You should not respond to this context unless it is highly relevant to your task.
</system-reminder>`,
isMeta: true,
}),
...messages,
]
}
你的 CLAUDE.md 被包装在 <system-reminder> 标签里,作为第一条 user message 插入到对话开头。不是 system prompt,是 user message。
这样会导致几个直接后果:
-
CLAUDE.md 的优先级低于 system prompt。System prompt 是模型的"宪法",user message 是"法律"。当两者冲突时,system prompt 通常胜出。
-
模型被告知这些内容"可能不相关"——注意那句
IMPORTANT: this context may or may not be relevant to your tasks。这是一个显式的降权信号。 -
但是——CLAUDE.md 的开头有一句强指令(
utils/claudemd.ts:89):
Codebase and user instructions are shown below. Be sure to adhere to these
instructions. IMPORTANT: These instructions OVERRIDE any default behavior
and you MUST follow them exactly as written.
“OVERRIDE any default behavior” + “MUST follow them exactly” —— 这是在用强语气对抗降权信号。效果取决于指令的具体性:越具体的指令越容易被遵守,越模糊的越容易被忽略。
所以别在 CLAUDE.md 里写"尽量简洁"这种模糊指令。写"回答不超过 3 句话"或"不要添加注释到未修改的代码"这种可验证的具体指令。
2. 从 CLAUDE 向上遍历到根目录
CLAUDE.md 不只是项目根目录的一个文件。Claude Code 从当前目录开始,逐级向上遍历到文件系统根目录,在每一级检查以下文件(utils/claudemd.ts:790-1075):
每一级目录检查:
├── CLAUDE.md → Project 类型(可版本控制)
├── .claude/CLAUDE.md → Project 类型(可版本控制)
├── .claude/rules/*.md → Project 类型(可版本控制,递归扫描子目录)
└── CLAUDE.local.md → Local 类型(不应提交到版本控制)
加上两个全局位置:
~/.claude/CLAUDE.md → User 类型(个人全局指令)
~/.claude/rules/*.md → User 类型(个人全局规则)
/etc/claude-code/CLAUDE.md → Managed 类型(企业管理员策略)
/etc/claude-code/.claude/rules/*.md → Managed 类型
加载顺序决定优先级。源码注释写得很清楚:
Files are loaded in reverse order of priority, i.e. the latest files
are highest priority with the model paying more attention to them.
“最后加载的优先级最高”——因为模型对靠近输入末尾的内容关注度更高(recency bias)。
完整的加载顺序(从先到后,优先级从低到高):
1. /etc/claude-code/CLAUDE.md (Managed - 企业策略)
2. /etc/claude-code/.claude/rules/*.md (Managed)
3. ~/.claude/CLAUDE.md (User - 个人全局)
4. ~/.claude/rules/*.md (User)
5. /repo-root/CLAUDE.md (Project - 仓库根)
6. /repo-root/.claude/CLAUDE.md (Project)
7. /repo-root/.claude/rules/*.md (Project)
8. /repo-root/src/CLAUDE.md (Project - 更近的目录)
9. /repo-root/src/.claude/rules/*.md (Project)
10. /repo-root/src/feature/CLAUDE.md (Project - 当前目录)
11. /repo-root/src/feature/CLAUDE.local.md (Local - 最高优先级)
规则放在仓库根目录的 .claude/rules/ 下。
个人偏好(语言、风格)放在 ~/.claude/CLAUDE.md
子目录特定规则(比如 frontend/CLAUDE.md 说"用 React 不用 Vue")放在对应目录
离当前工作目录越近的文件,优先级越高
3. @include 指令引入外部文件
CLAUDE.md 支持 @ 语法引入其他文件(utils/claudemd.ts:451-535):
# 我的项目规则
@./coding-standards.md
@~/global-rules.md
@/etc/team-rules/backend.md
解析逻辑:
@./path 或 @path — 相对于当前 CLAUDE.md 文件的路径
@~/path — 相对于 home 目录
@/path — 绝对路径
支持转义空格:@./my\ file.md
支持 #fragment 后缀(会被忽略):@./rules.md#section-1
安全限制:
-
扩展名白名单——只能引入文本文件。支持的扩展名包括
.md、.txt、.json、.yaml、.ts、.py、.go、.rs、.sql等约 80 种(完整列表见TEXT_FILE_EXTENSIONS)。二进制文件被静默忽略。 -
循环引用检测——
processedPathsSet 追踪已处理的文件路径(规范化后比较)。最大递归深度MAX_INCLUDE_DEPTH。 -
代码块免疫——
@指令只在 Markdown 的文本节点(leaf text nodes)中生效。代码块和行内代码中的@不会被解析:
// 这些不会被解析为 include:
if (element.type === 'code' || element.type === 'codespan') {
continue
}
-
外部文件需审批——引入工作目录以外的文件需要
claudeMdExternalIncludesApproved配置项启用。User 类型的 CLAUDE.md 默认可以引入外部文件,Project 类型需要显式审批。 -
不存在的文件静默忽略——不会报错,不会中断加载。
可以用 @include 把 CLAUDE.md 拆成模块:
# 项目根目录 CLAUDE.md
@.claude/rules/code-style.md
@.claude/rules/git-conventions.md
@.claude/rules/testing.md
4. Memory 系统
Memory 系统(memdir/)和 CLAUDE.md 是两套独立的机制,但它们在 system prompt 中紧挨着。
Memory 通过 loadMemoryPrompt() 加载(memdir/memdir.ts:419-507),注入到 system prompt 的动态部分——在 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之后。
MEMORY.md 有硬编码的截断限制(memdir/memdir.ts:35-38):
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000 // 25KB
截断逻辑(truncateEntrypointContent):
- 先按行截断——超过 200 行就砍到 200 行
- 再按字节截断——超过 25KB 就在最后一个换行符处切断
- 截断后追加警告:
> WARNING: MEMORY.md is 350 lines (limit: 200). Only part of it was loaded.
> Keep index entries to one line under ~200 chars; move detail into topic files.
所以 MEMORY.md 应该当索引用,不是写内容的地方。每条记忆写一行,控制在 200 字符以内,详细内容放单独的 topic 文件。
错误写法:
## 用户偏好
用户是一名高级 Go 开发者,有 10 年经验,主要在 Linux 环境下工作。
偏好简洁代码,不喜欢过度抽象。使用 Neovim 编辑器...(大量详细描述)
正确写法:
- [User Profile](user_profile.md) — 高级 Go 开发者,10年经验,偏好简洁
5. System Prompt 22 层概览
Claude Code 的 system prompt 由 22 个模块拼接(constants/prompts.ts:444-577),分三层缓存策略:
┌─── 静态层(全局可缓存,跨用户复用)────────────────────────────┐
│ 1. Attribution Header(指纹校验) │
│ 2. CLI 前缀标识 │
│ 3. 身份介绍:"You are Claude Code..." │
│ 4. 系统规则(prompt injection 警告等) │
│ 5. 编码任务指南 │
│ 6. 操作谨慎性指南 │
│ 7. 工具使用指南 │
│ 8. 语气风格 │
│ 9. 输出效率 │
├─── SYSTEM_PROMPT_DYNAMIC_BOUNDARY ───────────────────────┤
│ │
│ ── 动态层(会话级缓存,/clear 或 /compact 后重算)── │
│ 10. 会话指导 │
│ 11. ★ Memory 内容(loadMemoryPrompt) │
│ 12. 环境信息(平台、shell、模型名、Git 状态) │
│ 13. 语言偏好 │
│ 14. 输出样式 │
│ │
│ ── 易变层(每轮重算,标记为 DANGEROUS_uncached)── │
│ 15. MCP 服务器指令 │
│ 16. 暂存板指令 │
│ 17. 函数结果清除提醒 │
│ 18. 工具结果摘要 │
│ │
│ ── 条件注入 ── │
│ 19-22. token 预算 / Advisor 指令 / 系统上下文 │
└──────────────────────────────────────────────────────────┘
然后,在 system prompt 之外:
┌─── User Message 层 ─────────────────────────────────────┐
│ ★ CLAUDE.md 内容(包装在 <system-reminder> 中) │
│ 作为对话的第一条 user message │
├──────────────────────────────────────────────────────────┤
│ 用户的第一条实际消息 │
│ ...后续对话... │
└──────────────────────────────────────────────────────────┘
注意两个星号(★)标记的位置:
Memory 在 system prompt 的动态层(第 11 位)
CLAUDE.md 在 user message 层(对话的第一条消息)
它们的定位不同:
Memory 是 system prompt 的一部分,模型会将其视为"系统级指令"
CLAUDE.md 是 user message,模型将其视为"用户提供的上下文"
但 CLAUDE.md 有那句 “OVERRIDE any default behavior” 来提升权重。
6. 缓存经济学
三层结构直接影响 API 成本。
静态层(第 1-9 个模块)使用 cacheScope: 'global'——跨所有用户和组织复用。你不需要为这些 token 付费(cache hit)。
动态层(第 10-14 个模块)使用 cacheScope: 'org'——在你的组织/账户内复用。第一次会话需要创建缓存,后续会话 cache hit。
易变层(第 15-18 个模块)标记为 DANGEROUS_uncached——每轮重新计算。这些会破坏缓存。源码要求每个易变 section 必须注明破坏原因:
DANGEROUS_uncachedSystemPromptSection(
'mcp_instructions',
() => getMcpInstructionsSection(mcpClients),
'MCP servers connect/disconnect between turns', // 必须说明原因
)
CLAUDE.md 在 user message 中,不在 system prompt 中,所以它不影响 system prompt 的缓存。你修改 CLAUDE.md 不会导致 system prompt 缓存失效。但 CLAUDE.md 本身作为 user message 的一部分,会被 prompt caching 机制缓存——只要内容不变,后续轮次不需要重新处理这些 token。
几个点注意一下:
- 不要频繁修改 CLAUDE.md——每次修改都会使 user message 缓存失效
- MCP 服务器配置变化是免费的——它在易变层,本来就每轮重算
/compact和/clear会清除所有动态层缓存——Memory、环境信息等都会重新加载
7. .claude/rules/ 的递归发现
.claude/rules/ 目录支持子目录递归(utils/claudemd.ts:697-788)。你可以这样组织:
.claude/
├── CLAUDE.md
└── rules/
├── general.md
├── frontend/
│ ├── react.md
│ └── testing.md
└── backend/
├── api-design.md
└── database.md
所有 .md 文件都会被递归发现和加载。非 .md 文件被忽略。
rules 目录的处理有一个特殊逻辑:symlink 安全。代码会解析 symlink 的真实路径,并在 visitedDirs 和 processedPaths 中同时记录原始路径和解析后的路径,防止 symlink 循环。
8. Git Worktree 去重
如果你在 Git worktree 中工作,CLAUDE.md 的发现有一个特殊处理(utils/claudemd.ts:860-875):
// When running from a git worktree nested inside its main repo,
// the upward walk passes through both the worktree root and the
// main repo root. Both contain checked-in files like CLAUDE.md,
// so the same content gets loaded twice.
Claude Code 会检测你是否在一个嵌套 worktree 中(worktree 目录在主仓库目录内部)。如果是,它会跳过主仓库中的 Project 类型文件——因为 worktree 已经有自己的 checkout。
但 CLAUDE.local.md 不受影响——它是 gitignore 的,只存在于主仓库,不会被 worktree 复制。
9. 一些未曾使用过的功能
功能 1:--bare 模式跳过 CLAUDE.md
const shouldDisableClaudeMd =
isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
(isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)
如果你用 claude --bare 或设置了 CLAUDE_CODE_DISABLE_CLAUDE_MDS=1,所有 CLAUDE.md 都不会加载。--bare 的语义是"跳过我没明确要求的东西"。
功能 2:settings.json 可以关闭特定来源
if (isSettingSourceEnabled('projectSettings')) {
// 才会加载 Project 类型的 CLAUDE.md
}
if (isSettingSourceEnabled('localSettings')) {
// 才会加载 Local 类型的 CLAUDE.local.md
}
企业管理员可以通过 policy settings 禁用 Project 或 Local 类型的 CLAUDE.md。如果你的指令不生效,检查 settings.json 的 settingSources 配置。
功能 3:GrowthBook feature flag 可以跳过 Project 和 Local
const skipProjectLevel = getFeatureValue_CACHED_MAY_BE_STALE(
'tengu_paper_halyard', false,
)
if (skipProjectLevel && (file.type === 'Project' || file.type === 'Local'))
continue
Anthropic 内部有一个 feature flag tengu_paper_halyard,开启后会跳过所有 Project 和 Local 类型的 CLAUDE.md。这个 flag 目前默认关闭,但说明 Anthropic 保留了从服务端禁用项目级指令的能力。
10. 我推荐的配置方式
重构 CLAUDE.md
# 项目根目录 CLAUDE.md
@.claude/rules/code-style.md
@.claude/rules/git-workflow.md
@.claude/rules/testing.md
每个规则文件一个主题,一个文件不超过 50 行。
写具体的、可验证的指令
不要写:
代码要简洁
要写:
- 函数不超过 30 行
- 不要添加注释到你没有修改的代码行
- commit message 用中文,不超过 50 字
多利用优先级层级
全局偏好放 ~/.claude/CLAUDE.md:
- 用中文回答
- 不要在回答末尾总结你做了什么
项目规则放 项目根目录/.claude/rules/:
# .claude/rules/backend.md
- 使用 Go 1.22+ 的新特性
- 错误处理用 errors.Join 而不是 fmt.Errorf
- 测试用 testify
子目录规则放对应目录的 CLAUDE.md:
# frontend/CLAUDE.md
- 组件用函数组件,不用 class 组件
- 状态管理用 zustand
网友解答:
--【壹】--:
先赞后看
--【贰】--:
学习一下,追更
--【叁】--:
厉害,学习了
--【肆】--:
前排学习一下
--【伍】--:
佬太用心了
--【陆】--:
前排学习
--【柒】--:
感谢大佬。
--【捌】--:
感谢分享
--【玖】--:
好贴,赞了
--【拾】--:
学习了,感谢分享
--【拾壹】--:
好文,多发
--【拾贰】--:
感谢分享,
--【拾叁】--:
前排学习
--【拾肆】--:
相当详细的分析!前排学习
--【拾伍】--:
已加入阅读收藏
--【拾陆】--:
太强了佬
--【拾柒】--:
学AI,上L站!
--【拾捌】--:
学习,感谢分享
--【拾玖】--:
感谢分享
新一期来啦:
深入浅出 Claude Code(二):从源码解构 MEMORY.md,重塑你的上下文注入链路 开发调优上一篇我们重写了 CLAUDE.md,但这只是表层。今天这篇,我们将顺着代码的调用栈,扒开 Claude Code 的记忆系统,看看你的指令在底层究竟被赋予了什么级别的权重。 你让 Claude Code "记住"的东西,下次对话它可能完全不记得。或者记得,但不照做。 其实不是功能坏了——是你不清楚这些 memory 在系统里是怎么流动的。存在哪、怎么加载、权重多高、什么格式才有效,搞清楚这条…
大多数人写 CLAUDE.md 的方式是:随便写几条规则,感觉效果不明显,然后放弃。
你不知道这些文字在 Claude 眼里长什么样、被放在哪里、和其他指令的优先级关系是什么。
今天我来带各位佬友们剖析一下 claudecode 的底层逻辑
全网统一小恐龙头像,在别的地方看到的家人们点点赞
1. CLAUDE.md 不在 System Prompt 里
这是最大的认知误区。
大多数人以为 CLAUDE.md 的内容被拼接到 system prompt 中。不是的。
源码中,CLAUDE.md 通过 prependUserContext() 函数注入(utils/api.ts:449-474):
export function prependUserContext(messages, context): Message[] {
return [
createUserMessage({
content: `<system-reminder>
As you answer the user's questions, you can use the following context:
# claudeMd
${claudeMdContent}
IMPORTANT: this context may or may not be relevant to your tasks.
You should not respond to this context unless it is highly relevant to your task.
</system-reminder>`,
isMeta: true,
}),
...messages,
]
}
你的 CLAUDE.md 被包装在 <system-reminder> 标签里,作为第一条 user message 插入到对话开头。不是 system prompt,是 user message。
这样会导致几个直接后果:
-
CLAUDE.md 的优先级低于 system prompt。System prompt 是模型的"宪法",user message 是"法律"。当两者冲突时,system prompt 通常胜出。
-
模型被告知这些内容"可能不相关"——注意那句
IMPORTANT: this context may or may not be relevant to your tasks。这是一个显式的降权信号。 -
但是——CLAUDE.md 的开头有一句强指令(
utils/claudemd.ts:89):
Codebase and user instructions are shown below. Be sure to adhere to these
instructions. IMPORTANT: These instructions OVERRIDE any default behavior
and you MUST follow them exactly as written.
“OVERRIDE any default behavior” + “MUST follow them exactly” —— 这是在用强语气对抗降权信号。效果取决于指令的具体性:越具体的指令越容易被遵守,越模糊的越容易被忽略。
所以别在 CLAUDE.md 里写"尽量简洁"这种模糊指令。写"回答不超过 3 句话"或"不要添加注释到未修改的代码"这种可验证的具体指令。
2. 从 CLAUDE 向上遍历到根目录
CLAUDE.md 不只是项目根目录的一个文件。Claude Code 从当前目录开始,逐级向上遍历到文件系统根目录,在每一级检查以下文件(utils/claudemd.ts:790-1075):
每一级目录检查:
├── CLAUDE.md → Project 类型(可版本控制)
├── .claude/CLAUDE.md → Project 类型(可版本控制)
├── .claude/rules/*.md → Project 类型(可版本控制,递归扫描子目录)
└── CLAUDE.local.md → Local 类型(不应提交到版本控制)
加上两个全局位置:
~/.claude/CLAUDE.md → User 类型(个人全局指令)
~/.claude/rules/*.md → User 类型(个人全局规则)
/etc/claude-code/CLAUDE.md → Managed 类型(企业管理员策略)
/etc/claude-code/.claude/rules/*.md → Managed 类型
加载顺序决定优先级。源码注释写得很清楚:
Files are loaded in reverse order of priority, i.e. the latest files
are highest priority with the model paying more attention to them.
“最后加载的优先级最高”——因为模型对靠近输入末尾的内容关注度更高(recency bias)。
完整的加载顺序(从先到后,优先级从低到高):
1. /etc/claude-code/CLAUDE.md (Managed - 企业策略)
2. /etc/claude-code/.claude/rules/*.md (Managed)
3. ~/.claude/CLAUDE.md (User - 个人全局)
4. ~/.claude/rules/*.md (User)
5. /repo-root/CLAUDE.md (Project - 仓库根)
6. /repo-root/.claude/CLAUDE.md (Project)
7. /repo-root/.claude/rules/*.md (Project)
8. /repo-root/src/CLAUDE.md (Project - 更近的目录)
9. /repo-root/src/.claude/rules/*.md (Project)
10. /repo-root/src/feature/CLAUDE.md (Project - 当前目录)
11. /repo-root/src/feature/CLAUDE.local.md (Local - 最高优先级)
规则放在仓库根目录的 .claude/rules/ 下。
个人偏好(语言、风格)放在 ~/.claude/CLAUDE.md
子目录特定规则(比如 frontend/CLAUDE.md 说"用 React 不用 Vue")放在对应目录
离当前工作目录越近的文件,优先级越高
3. @include 指令引入外部文件
CLAUDE.md 支持 @ 语法引入其他文件(utils/claudemd.ts:451-535):
# 我的项目规则
@./coding-standards.md
@~/global-rules.md
@/etc/team-rules/backend.md
解析逻辑:
@./path 或 @path — 相对于当前 CLAUDE.md 文件的路径
@~/path — 相对于 home 目录
@/path — 绝对路径
支持转义空格:@./my\ file.md
支持 #fragment 后缀(会被忽略):@./rules.md#section-1
安全限制:
-
扩展名白名单——只能引入文本文件。支持的扩展名包括
.md、.txt、.json、.yaml、.ts、.py、.go、.rs、.sql等约 80 种(完整列表见TEXT_FILE_EXTENSIONS)。二进制文件被静默忽略。 -
循环引用检测——
processedPathsSet 追踪已处理的文件路径(规范化后比较)。最大递归深度MAX_INCLUDE_DEPTH。 -
代码块免疫——
@指令只在 Markdown 的文本节点(leaf text nodes)中生效。代码块和行内代码中的@不会被解析:
// 这些不会被解析为 include:
if (element.type === 'code' || element.type === 'codespan') {
continue
}
-
外部文件需审批——引入工作目录以外的文件需要
claudeMdExternalIncludesApproved配置项启用。User 类型的 CLAUDE.md 默认可以引入外部文件,Project 类型需要显式审批。 -
不存在的文件静默忽略——不会报错,不会中断加载。
可以用 @include 把 CLAUDE.md 拆成模块:
# 项目根目录 CLAUDE.md
@.claude/rules/code-style.md
@.claude/rules/git-conventions.md
@.claude/rules/testing.md
4. Memory 系统
Memory 系统(memdir/)和 CLAUDE.md 是两套独立的机制,但它们在 system prompt 中紧挨着。
Memory 通过 loadMemoryPrompt() 加载(memdir/memdir.ts:419-507),注入到 system prompt 的动态部分——在 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之后。
MEMORY.md 有硬编码的截断限制(memdir/memdir.ts:35-38):
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000 // 25KB
截断逻辑(truncateEntrypointContent):
- 先按行截断——超过 200 行就砍到 200 行
- 再按字节截断——超过 25KB 就在最后一个换行符处切断
- 截断后追加警告:
> WARNING: MEMORY.md is 350 lines (limit: 200). Only part of it was loaded.
> Keep index entries to one line under ~200 chars; move detail into topic files.
所以 MEMORY.md 应该当索引用,不是写内容的地方。每条记忆写一行,控制在 200 字符以内,详细内容放单独的 topic 文件。
错误写法:
## 用户偏好
用户是一名高级 Go 开发者,有 10 年经验,主要在 Linux 环境下工作。
偏好简洁代码,不喜欢过度抽象。使用 Neovim 编辑器...(大量详细描述)
正确写法:
- [User Profile](user_profile.md) — 高级 Go 开发者,10年经验,偏好简洁
5. System Prompt 22 层概览
Claude Code 的 system prompt 由 22 个模块拼接(constants/prompts.ts:444-577),分三层缓存策略:
┌─── 静态层(全局可缓存,跨用户复用)────────────────────────────┐
│ 1. Attribution Header(指纹校验) │
│ 2. CLI 前缀标识 │
│ 3. 身份介绍:"You are Claude Code..." │
│ 4. 系统规则(prompt injection 警告等) │
│ 5. 编码任务指南 │
│ 6. 操作谨慎性指南 │
│ 7. 工具使用指南 │
│ 8. 语气风格 │
│ 9. 输出效率 │
├─── SYSTEM_PROMPT_DYNAMIC_BOUNDARY ───────────────────────┤
│ │
│ ── 动态层(会话级缓存,/clear 或 /compact 后重算)── │
│ 10. 会话指导 │
│ 11. ★ Memory 内容(loadMemoryPrompt) │
│ 12. 环境信息(平台、shell、模型名、Git 状态) │
│ 13. 语言偏好 │
│ 14. 输出样式 │
│ │
│ ── 易变层(每轮重算,标记为 DANGEROUS_uncached)── │
│ 15. MCP 服务器指令 │
│ 16. 暂存板指令 │
│ 17. 函数结果清除提醒 │
│ 18. 工具结果摘要 │
│ │
│ ── 条件注入 ── │
│ 19-22. token 预算 / Advisor 指令 / 系统上下文 │
└──────────────────────────────────────────────────────────┘
然后,在 system prompt 之外:
┌─── User Message 层 ─────────────────────────────────────┐
│ ★ CLAUDE.md 内容(包装在 <system-reminder> 中) │
│ 作为对话的第一条 user message │
├──────────────────────────────────────────────────────────┤
│ 用户的第一条实际消息 │
│ ...后续对话... │
└──────────────────────────────────────────────────────────┘
注意两个星号(★)标记的位置:
Memory 在 system prompt 的动态层(第 11 位)
CLAUDE.md 在 user message 层(对话的第一条消息)
它们的定位不同:
Memory 是 system prompt 的一部分,模型会将其视为"系统级指令"
CLAUDE.md 是 user message,模型将其视为"用户提供的上下文"
但 CLAUDE.md 有那句 “OVERRIDE any default behavior” 来提升权重。
6. 缓存经济学
三层结构直接影响 API 成本。
静态层(第 1-9 个模块)使用 cacheScope: 'global'——跨所有用户和组织复用。你不需要为这些 token 付费(cache hit)。
动态层(第 10-14 个模块)使用 cacheScope: 'org'——在你的组织/账户内复用。第一次会话需要创建缓存,后续会话 cache hit。
易变层(第 15-18 个模块)标记为 DANGEROUS_uncached——每轮重新计算。这些会破坏缓存。源码要求每个易变 section 必须注明破坏原因:
DANGEROUS_uncachedSystemPromptSection(
'mcp_instructions',
() => getMcpInstructionsSection(mcpClients),
'MCP servers connect/disconnect between turns', // 必须说明原因
)
CLAUDE.md 在 user message 中,不在 system prompt 中,所以它不影响 system prompt 的缓存。你修改 CLAUDE.md 不会导致 system prompt 缓存失效。但 CLAUDE.md 本身作为 user message 的一部分,会被 prompt caching 机制缓存——只要内容不变,后续轮次不需要重新处理这些 token。
几个点注意一下:
- 不要频繁修改 CLAUDE.md——每次修改都会使 user message 缓存失效
- MCP 服务器配置变化是免费的——它在易变层,本来就每轮重算
/compact和/clear会清除所有动态层缓存——Memory、环境信息等都会重新加载
7. .claude/rules/ 的递归发现
.claude/rules/ 目录支持子目录递归(utils/claudemd.ts:697-788)。你可以这样组织:
.claude/
├── CLAUDE.md
└── rules/
├── general.md
├── frontend/
│ ├── react.md
│ └── testing.md
└── backend/
├── api-design.md
└── database.md
所有 .md 文件都会被递归发现和加载。非 .md 文件被忽略。
rules 目录的处理有一个特殊逻辑:symlink 安全。代码会解析 symlink 的真实路径,并在 visitedDirs 和 processedPaths 中同时记录原始路径和解析后的路径,防止 symlink 循环。
8. Git Worktree 去重
如果你在 Git worktree 中工作,CLAUDE.md 的发现有一个特殊处理(utils/claudemd.ts:860-875):
// When running from a git worktree nested inside its main repo,
// the upward walk passes through both the worktree root and the
// main repo root. Both contain checked-in files like CLAUDE.md,
// so the same content gets loaded twice.
Claude Code 会检测你是否在一个嵌套 worktree 中(worktree 目录在主仓库目录内部)。如果是,它会跳过主仓库中的 Project 类型文件——因为 worktree 已经有自己的 checkout。
但 CLAUDE.local.md 不受影响——它是 gitignore 的,只存在于主仓库,不会被 worktree 复制。
9. 一些未曾使用过的功能
功能 1:--bare 模式跳过 CLAUDE.md
const shouldDisableClaudeMd =
isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
(isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)
如果你用 claude --bare 或设置了 CLAUDE_CODE_DISABLE_CLAUDE_MDS=1,所有 CLAUDE.md 都不会加载。--bare 的语义是"跳过我没明确要求的东西"。
功能 2:settings.json 可以关闭特定来源
if (isSettingSourceEnabled('projectSettings')) {
// 才会加载 Project 类型的 CLAUDE.md
}
if (isSettingSourceEnabled('localSettings')) {
// 才会加载 Local 类型的 CLAUDE.local.md
}
企业管理员可以通过 policy settings 禁用 Project 或 Local 类型的 CLAUDE.md。如果你的指令不生效,检查 settings.json 的 settingSources 配置。
功能 3:GrowthBook feature flag 可以跳过 Project 和 Local
const skipProjectLevel = getFeatureValue_CACHED_MAY_BE_STALE(
'tengu_paper_halyard', false,
)
if (skipProjectLevel && (file.type === 'Project' || file.type === 'Local'))
continue
Anthropic 内部有一个 feature flag tengu_paper_halyard,开启后会跳过所有 Project 和 Local 类型的 CLAUDE.md。这个 flag 目前默认关闭,但说明 Anthropic 保留了从服务端禁用项目级指令的能力。
10. 我推荐的配置方式
重构 CLAUDE.md
# 项目根目录 CLAUDE.md
@.claude/rules/code-style.md
@.claude/rules/git-workflow.md
@.claude/rules/testing.md
每个规则文件一个主题,一个文件不超过 50 行。
写具体的、可验证的指令
不要写:
代码要简洁
要写:
- 函数不超过 30 行
- 不要添加注释到你没有修改的代码行
- commit message 用中文,不超过 50 字
多利用优先级层级
全局偏好放 ~/.claude/CLAUDE.md:
- 用中文回答
- 不要在回答末尾总结你做了什么
项目规则放 项目根目录/.claude/rules/:
# .claude/rules/backend.md
- 使用 Go 1.22+ 的新特性
- 错误处理用 errors.Join 而不是 fmt.Errorf
- 测试用 testify
子目录规则放对应目录的 CLAUDE.md:
# frontend/CLAUDE.md
- 组件用函数组件,不用 class 组件
- 状态管理用 zustand
网友解答:
--【壹】--:
先赞后看
--【贰】--:
学习一下,追更
--【叁】--:
厉害,学习了
--【肆】--:
前排学习一下
--【伍】--:
佬太用心了
--【陆】--:
前排学习
--【柒】--:
感谢大佬。
--【捌】--:
感谢分享
--【玖】--:
好贴,赞了
--【拾】--:
学习了,感谢分享
--【拾壹】--:
好文,多发
--【拾贰】--:
感谢分享,
--【拾叁】--:
前排学习
--【拾肆】--:
相当详细的分析!前排学习
--【拾伍】--:
已加入阅读收藏
--【拾陆】--:
太强了佬
--【拾柒】--:
学AI,上L站!
--【拾捌】--:
学习,感谢分享
--【拾玖】--:
感谢分享

