• Khalil_Gao

    新一期来啦:

    上一篇我们重写了 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)。二进制文件被静默忽略。

    - 循环引用检测——processedPaths Set 追踪已处理的文件路径(规范化后比较)。最大递归深度 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
    主楼
  • KWang

    学AI,上L站!

    #1
  • Znwhyo

    感谢分享^-^

    #2
  • 树洞小兔

    先赞后看

    #3
  • 炫彩小鱼干

    相当详细的分析!前排学习

    #4
  • shawn89

    前排学习

    #5
  • 昔日织

    学习了,感谢分享^-^

    #6
  • sharkly

    好贴,赞了

    #7
  • 老路

    感谢分享

    #8
  • 蓦言﹉

    厉害,学习了 ^-^

    #9
  • jawi

    佬太用心了

    #10
  • YQF

    感谢分享,

    #11
  • 不吃猪肉

    前排学习一下

    #12
  • 大帅哥

    感谢大佬。

    #13
  • 北川

    学习一下,追更

    #14
  • Sokoly

    前排学习

    #15
  • SIX_MAN

    太强了佬

    #16
  • waken

    好文,多发

    #17
  • 希望的葱

    已加入阅读收藏 ^-^

    #18
  • 遗忘的风

    学习,感谢分享

    #19

发表回复

登录后回复