Back to Blog

Claude Code Harness 第19章:CLAUDE.md——用户指令作为覆盖层

2026-04-05
Claude Code Configuration CLAUDE.md User Instructions

Claude Code Harness 第19章:CLAUDE.md——用户指令作为覆盖层

系列说明:本文是《Claude Code Harness:从提示词到 AI 工程化实践》第19章,完整目录请参考系列导读

在之前的章节中,我们探索了 Claude Code 的诸多核心机制:从工具定义(第3-5章)、权限控制(第6-7章)、会话管理(第8-10章)、到 Hooks 系统(第18章)。这些章节展现了一个共同的主题:通过代码执行扩展 Agent 行为

本章将探讨另一个同样重要的维度:通过自然语言指令控制模型输出。如果说 Hooks 是"硬扩展"通道,那么 CLAUDE.md 就是"软控制"通道——它不是配置文件,而是一套完整的指令注入系统,具有四级优先级层叠、传递性文件包含、路径范围限定、HTML 注释剥离,以及明确的覆盖语义声明。

CLAUDE.md 的设计哲学可以用一句话概括:

用户的指令覆盖模型的默认行为。

这句话不是修辞——它被字面注入到系统提示词中。本章将从文件发现、内容处理、到最终注入提示词的完整链路,深入剖析这套系统的实现原理和最佳实践。


19.1 为什么 CLAUDE.md 至关重要

在深入技术细节之前,让我们先理解 CLAUDE.md 系统解决的核心问题。

问题背景

AI Agent 的行为受两方面控制:

  1. 硬编码逻辑:工具定义、权限系统、执行流程等代码层面的控制
  2. 提示词工程:通过自然语言指令引导模型输出

CLAUDE.md 系统关注的是第二方面。在实际开发中,不同层级的用户有不同的指令需求:

  • 企业管理员:需要推送安全策略、合规要求
  • 团队负责人:需要定义项目规范、技术栈约定
  • 个人开发者:需要个性化设置、编程风格偏好

CLAUDE.md 通过分层覆盖架构满足这些需求,每一层都可以被下一层覆盖或补充。

与其他系统的关系

CLAUDE.md 系统与之前章节探讨的多个系统密切相关:

系统 控制维度 优先级机制 典型场景
Hooks 代码执行 前置/后置钩子 自动化测试、代码格式化
Permissions 工具访问 权限表 + 提示词注入 安全限制、资源保护
CLAUDE.md 自然语言指令 四级优先级层叠 代码规范、项目约定

Hooks 和 CLAUDE.md 并不冲突——它们共同构成用户的"驾驭层"(harness layer)。Hooks 通过代码执行自动化任务,CLAUDE.md 通过指令声明引导模型决策。


19.2 四级加载优先级架构

CLAUDE.md 系统采用四级优先级模型。文件按反向优先级顺序加载——最后加载的优先级最高,因为 LLM 对对话末尾的内容关注度更高(recency bias)。

优先级层级概览

flowchart TB
    subgraph L1 ["Level 1: Managed Memory(最低优先级,最先加载)"]
        M1["/etc/claude-code/CLAUDE.md
企业策略推送
适用于所有用户"] end subgraph L2 ["Level 2: User Memory"] M2["~/.claude/CLAUDE.md
~/.claude/rules/*.md
用户私有全局指令
适用于所有项目"] end subgraph L3 ["Level 3: Project Memory"] M3["CLAUDE.md, .claude/CLAUDE.md
.claude/rules/*.md
从项目根到 CWD 每层遍历
提交到 git,团队共享"] end subgraph L4 ["Level 4: Local Memory(最高优先级,最后加载)"] M4["CLAUDE.local.md
已 gitignore
仅本地生效"] end L1 -->|"被覆盖"| L2 -->|"被覆盖"| L3 -->|"被覆盖"| L4 style L4 fill:#e6f3e6,stroke:#2d862d,stroke-width:3px style L1 fill:#f3e6e6,stroke:#862d2d,stroke-width:1px

Level 1:Managed Memory(企业策略)

路径/etc/claude-code/CLAUDE.md

特点

  • 通过 MDM(Mobile Device Management)推送
  • 适用于组织内的所有用户和所有项目
  • 优先级最低——可被其他层级覆盖
  • 企业 IT 部门用于强制执行安全策略、合规要求

典型用例

# /etc/claude-code/CLAUDE.md

企业安全策略:
- 禁止在代码中硬编码密钥和密码
- 所有 API 调用必须通过企业网关
- 敏感数据必须加密存储
- 遵循 OWASP 安全最佳实践

Level 2:User Memory(个人全局配置)

路径

  • ~/.claude/CLAUDE.md
  • ~/.claude/rules/*.md

特点

  • 用户私有配置,不提交到 git
  • 适用于该用户的所有项目
  • 优先级高于 Managed Memory
  • 用于设置个人编程风格、偏好设置

特权机制:User Memory 层级的 @include 指令可以引用项目目录外的文件(includeExternal: true),这是其他层级不具备的权限。

典型用例

# ~/.claude/CLAUDE.md

个人编码风格:
- 使用 2 空格缩进
- 偏好函数式编程风格
- 变量命名使用 camelCase
- 注释使用中文

工具链偏好:
- 优先使用 pnpm 而非 npm
- 测试框架使用 Vitest
- 代码检查工具:ESLint + Prettier

Level 3:Project Memory(项目级配置)

路径

  • CLAUDE.md(项目根目录)
  • .claude/CLAUDE.md
  • .claude/rules/*.md

特点

  • 从当前工作目录(CWD)向上遍历到文件系统根目录
  • 每层目录的 CLAUDE.md.claude/rules/*.md 都会被加载
  • 距离 CWD 越近的文件优先级越高
  • 提交到 git,团队共享

遍历逻辑

flowchart TD
    A["开始:当前工作目录
/home/user/project/src/components"] --> B["向上遍历"] B --> C["/home/user/project/src/components
加载 CLAUDE.md(如果存在)"] C --> D["/home/user/project/src
加载 CLAUDE.md(如果存在)"] D --> E["/home/user/project
加载 CLAUDE.md
加载 .claude/CLAUDE.md
加载 .claude/rules/*.md"] E --> F["/home/user
加载 CLAUDE.md(如果存在)"] F --> G["/home
加载 CLAUDE.md(如果存在)"] G --> H["/(文件系统根目录)
停止遍历"] style E fill:#e6f3ff,stroke:#0066cc style H fill:#f0f0f0,stroke:#999999

典型用例

# /home/user/project/CLAUDE.md

项目技术栈:
- 前端框架:Next.js 14 + TypeScript
- 状态管理:Zustand
- 样式方案:Tailwind CSS
- 测试框架:Jest + React Testing Library

架构规范:
- 所有 API 调用必须通过 /services/api.ts 统一入口
- 组件按 feature 分组到 /features 目录
- 共享类型定义放在 /types 目录
- 环境变量使用 .env.local 管理

Level 4:Local Memory(本地覆盖)

路径CLAUDE.local.md(任意层级)

特点

  • 优先级最高,最后加载
  • 已被 .gitignore 排除,仅本地生效
  • 用于临时覆盖、调试配置、个人工具链

典型用例

# CLAUDE.local.md

本地开发配置:
- 启用详细日志输出
- 使用本地 Mock 数据而非真实 API
- 跳过时间密集型测试

个人调试工具:
- 优先使用 Bun 而非 Node.js
- 启用 Hot Module Replacement

配置源开关

每一级(除 Managed 外)的加载都受配置源开关控制:

配置源 控制层级 SDK 默认值
userSettings User Memory false
projectSettings Project Memory false
localSettings Local Memory false

SDK 模式下默认将 settingSources 设为空数组,意味着除非显式启用,否则只有 Managed Memory 生效。这是最小权限原则的体现——SDK 使用者需要明确授予配置权限。


19.3 @include 指令:模块化指令组织

CLAUDE.md 支持 @include 语法来引用其他文件,实现模块化的指令组织。这避免了单个巨型配置文件,提升了可维护性。

语法格式

@include 使用简洁的 @ 前缀加路径语法:

语法 含义 示例
@path 相对于当前文件目录的路径 @./docs/api-rules.md
@./path 同上(显式相对路径) @./coding-standards.md
@~/path 相对于用户 home 目录 @~/.claude/snippets/react.md
@/absolute/path 绝对路径 @/usr/share/docs/standards.md
@path#section 带片段标识符(# 及之后被忽略) @docs.md#advanced
@path\ with\ spaces 反斜杠转义空格 @./my\ file.md

实际使用示例

# CLAUDE.md

## 项目概述

这是一个基于 Next.js 的全栈应用,遵循以下规范:

@./docs/coding-standards.md
@./docs/api-conventions.md
@./docs/testing-guide.md

## 全局规则

@~/.claude/rules/common-patterns.md

被引用的文件./docs/coding-standards.md):

# 编码规范

## TypeScript 规则

- 禁止使用 `any` 类型,使用 `unknown` 替代
- 所有函数必须有明确的返回类型注解
- 接口定义使用 `interface`,类型别名使用 `type`

## React 规则

- 组件使用函数式组件 + Hooks
- Props 必须定义接口
- 禁止在 JSX 中写复杂逻辑,提取到函数

路径提取机制

路径提取不是简单的字符串匹配——它使用 marked lexer 预处理 token 流,确保以下规则:

  1. 代码块中的 @ 被忽略:代码示例中的 @include 不会被误解析
  2. HTML 注释中的 @ 被忽略:注释内的指令不生效
  3. 仅处理文本节点:递归进入嵌套结构(列表、引用等)

示例

# 指令文件

@include config.md          # ✅ 会被解析

```typescript
// 这是代码块,@include 不会被解析
const path = "@include/test.md";

@include another.md # ✅ 会被解析


### 传递性包含与安全限制

#### 循环引用防护

`@include` 支持传递性包含(A 包含 B,B 包含 C),但有两个安全机制:

**最大深度限制**:最多 5 层嵌套

```mermaid
flowchart LR
    A["CLAUDE.md"] --> B["@include rules.md"]
    B --> C["@include api.md"]
    C --> D["@include db.md"]
    D --> E["@include utils.md"]
    E --> F["@include common.md"]
    F --> G["⚠️ 超过深度限制,停止解析"]
    
    style G fill:#f3e6e6,stroke:#cc0000

循环引用检测

# a.md
@include b.md

# b.md
@include a.md  # ⚠️ 检测到循环,已被处理过,跳过

实现原理:通过 processedPaths Set 追踪已处理的文件路径,路径在比较前经过规范化(处理 Windows 盘符大小写差异)。

外部文件安全

@include 指向项目目录外的文件时,默认不加载(安全边界)。只有以下情况允许外部包含:

  1. User Memory 层级~/.claude/CLAUDE.md 中的 @include 可以引用任何位置
  2. 用户显式批准:设置 hasClaudeMdExternalIncludesApproved: true

如果发现未批准的外部包含,系统会显示警告:

⚠️ Warning: CLAUDE.md contains @include directives to files outside the project directory.
These external includes were not loaded. To enable them, approve external includes in settings.

符号链接处理

每个文件在处理前都解析符号链接到真实路径。这防止了一个安全漏洞:攻击者可能通过符号链接绕过循环引用检测。

flowchart LR
    A["读取 CLAUDE.md"] --> B{是否为符号链接?}
    B -->|是| C["解析到真实路径"]
    B -->|否| D["直接使用路径"]
    C --> E["检查循环引用"]
    D --> E
    E --> F["处理文件内容"]
    
    style C fill:#fff3cd,stroke:#ff9800

19.4 frontmatter paths:按需加载规则

.claude/rules/ 目录中的文件可以通过 YAML frontmatter 的 paths 字段限定其适用范围。只有当 Claude 操作的文件路径匹配这些 glob 模式时,规则才会被注入上下文。

基本语法

---
paths: src/**/*.ts, tests/**/*.test.ts
---

# API 开发规范

所有 API 端点必须:
1. 使用 Zod 进行请求/响应验证
2. 返回标准化的错误响应
3. 包含 OpenAPI 文档注释

或 YAML 列表格式:

---
paths:
  - src/api/**/*.ts
  - src/api/**/*.test.ts
  - src/types/api.ts
---

# API 开发规范
...

花括号展开

支持 Bash 风格的花括号展开:

---
paths: src/*.{ts,tsx}, tests/**/*.test.{ts,tsx}
---

会被展开为:

paths:
  - src/*.ts
  - src/*.tsx
  - tests/**/*.test.ts
  - tests/**/*.test.tsx

多层花括号也被支持:

---
paths: src/{api,components}/{index,types}.{ts,tsx}
---

展开为:

paths:
  - src/api/index.ts
  - src/api/index.tsx
  - src/api/types.ts
  - src/api/types.tsx
  - src/components/index.ts
  - src/components/index.tsx
  - src/components/types.ts
  - src/components/types.tsx

YAML 解析的容错处理

glob 模式常包含 YAML 特殊字符({}[]*, &#!|>%@),直接解析会失败。系统使用两层容错机制:

第一次尝试:直接解析

---
paths: src/**/*.{ts,tsx}  # ❌ 解析失败:{ } 是特殊字符
---

第二次尝试:自动引用问题值

---
paths: "src/**/*.{ts,tsx}"  # ✅ 解析成功
---

这意味着用户可以直接写 paths: src/**/*.{ts,tsx} 而无需手动加引号——解析器会在第一次失败后自动修复。

条件规则 vs 无条件规则

.claude/rules/ 目录中的文件分为两类:

类型 frontmatter 加载时机 示例
无条件规则 paths 字段 会话启动时预加载 编码规范、通用约定
条件规则 paths 字段 操作匹配文件时按需加载 特定技术栈规则

无条件规则示例

# .claude/rules/general-conventions.md

# 通用约定

- 所有函数必须添加 JSDoc 注释
- 变量命名使用 camelCase,常量使用 UPPER_SNAKE_CASE
- 文件名使用 kebab-case

条件规则示例

---
paths: src/**/*.svelte
---

# Svelte 组件规范

- 使用 `