CC你一定要看看这个!——ClaudeCode如何做Harness(上下文策略篇1)
让CC不得不看的:Rule与Hook机制

你好,我是司沐。
从CC源码泄露到现在,已经差不多有两个月的时间了。Github上从原始代码到重写项目,再到出教程,已经积累了许多很不错的仓库。
由于我自己目前的主要工作就是Agent Harness,所以过去的两个月里也将CC的代码翻来覆去反复读。
在这个过程中,我发现目前许多对CC代码与设计的解析,看起来好像并非出自一个Agent工程师之手,而更像是一个转行到Agent,但同时保留了一些老行业思维的人,与并不是很懂Agent的AI一起协作出来的。这样当然也有许多可圈可点的地方,但总归少了点意思,并不能聚焦到CC真正的核心设计上。
所以,我计划在CC源码泄露已经过了两个月之后的当下,重新整理并发出一份我自己对CC中Harness工程设计的理解。
这个系列大概会出10篇左右,涵盖工具设计,上下文压缩策略,提示词编排策略,记忆策略,数据实体分层结构等等内容。
希望对读到文章的你有帮助。
上下文策略篇,会从设计角度解释各上下文组件如何产生、通过什么机制进入模型视野,以及何时离开。不涉及函数名和代码细节,面向希望理解或复刻这套机制的开发者。
在第一节中,我们会了解到CC中Rule机制与Hook机制的作用与设计。
Rules(条件规则)
.claude/rules/ 目录下可以放很多规则文件,把项目指令分散到多个文件里管理——比起把所有内容堆在一个 CLAUDE.md 里,这样的组织方式更清晰,不同模块的规范可以分开维护。
这些文件分两种。一种没有指定路径范围,和 CLAUDE.md 一样,在会话启动时就全部加载进来,没什么特别的。另一种在文件最顶部声明了一组路径模式,叫做"条件规则"——平时不加载,等到触发条件成立才出现。

如何写条件规则
规则文件是普通的 Markdown 文件,在文件最开头用三条短横线包住一段声明,写上这条规则适用于哪些文件路径,后面跟着规则内容。路径模式支持 glob 语法,也支持花括号展开多个后缀。
示例一:前端样式规范
项目里有大量 TypeScript 和 React 文件,但只想在 Claude 处理这类文件时才注入前端规范,其他时候不占用上下文。规则文件这样写:
---
paths: src/**/*.ts, src/**/*.tsx
---
## 前端代码规范
- 组件文件用 PascalCase 命名,工具函数文件用 camelCase
- 所有异步函数必须处理错误,不允许裸露的 Promise 链
- 禁止使用 any 类型,实在无法确定类型时用 unknown
- CSS 样式不写内联,统一放在同目录的 .module.css 文件里
保存到 .claude/rules/frontend.md。此后只要 Claude 读了 src/ 下任意 .ts 或 .tsx 文件,这条规则就会在下一轮推理前自动注入。读 Python 文件、读配置文件,不触发。
示例二:数据库迁移规范
数据库迁移文件有特别高的风险,需要额外的审查提示:
---
paths: migrations/**
---
## 数据库迁移注意事项
迁移文件一旦执行就不可逆,修改前务必确认以下几点:
- 所有 ALTER TABLE 操作必须有对应的回滚语句
- 删除列之前先确认应用代码已经不再引用该列
- 如果迁移会锁表,需要在注释里说明预估锁表时长
- 生产环境迁移需要在 PR 描述里附上在测试库的执行结果
保存到 .claude/rules/migrations.md。只有 Claude 读取 migrations/ 目录下的文件时,这段提示才会出现。
路径模式可以用逗号分隔写多个,也可以写成 YAML 列表的格式,两种写法等价:
---
paths:
- src/**/*.ts
- src/**/*.tsx
---
触发条件

只有一种触发方式:Claude 用读文件工具成功读取了某个文件。读取完成后,系统把这个文件路径拿去和所有条件规则声明的路径模式逐一比对,哪条规则匹配上了,就在下一轮向模型发请求前把那条规则的内容注入进去。
注意这里说的是"读文件",仅此一种。写文件、改文件、执行命令,通通不触发。原因也很直观:Claude 读了一个文件,说明它下一步要处理那部分代码,这是注入相关规范的最佳时机。如果在会话一开始就把所有规则全部塞进来,那些还没用到的规范只是在白白占用上下文窗口。
注入方式与去重
条件规则注入的方式和 CLAUDE.md 一样,也是作为一条用户消息插入对话历史,只是独立的一条,不和 CLAUDE.md 合并。一次对话里可能积累若干条这样的消息,第一条是 CLAUDE.md,后面的是被陆续触发的各条规则。
同一条规则在同一次会话里只注入一次,不管触发它的那个文件被读了多少遍。比如你一个下午读了二十次 src/components/Button.tsx,前端规范只在第一次读取后注入,不会重复出现。
Hooks(钩子)

工作方式
Hooks 的核心很简单:在会话生命周期的某些时刻,系统把当前事件的信息(JSON 格式)通过标准输入发给你配置的程序,程序处理后把结果从标准输出返回来。两边没有别的连接,就是这一来一往的数据交换,和 Unix 管道是同一种思路。
四种 Hook 类型
能接收事件的不只有 shell 脚本,系统支持四种形态:
- 命令(command):运行一个 shell 脚本。最基础的形式,适合做检查、验证、记日志、发系统通知等场景。
- HTTP 请求(http):把事件信息以 POST 请求发到你指定的 URL,适合把 Claude 的操作接入现有的 API 服务或 Webhook 体系,比如每次工具调用都记到外部审计系统。
- Prompt 推理(prompt):把事件信息交给一个小模型,让它用自然语言判断该怎么处理,结果返回给系统。适合逻辑复杂、难以用脚本规则写清楚的判断场景。
- Agent 运行(agent):启动一个完整的 Claude 会话来处理这个事件,相当于嵌套一个 agent。能力最强,代价也最大,默认超时 60 秒。
工具过滤:哪些操作会触发
每条 hook 可以限定只在某些工具被调用时触发:
- 不写限定:所有工具调用都触发
- 写一个工具名:只匹配那个工具(如只在写文件时触发)
- 用竖线分隔多个工具名:匹配其中任意一个(如读文件或写文件时都触发)
- 写其他内容:当作正则表达式来匹配工具名
结果如何生效

程序运行结束后,系统根据退出码和输出内容决定下一步,共三种情况:
第一种——传递信息给模型:正常退出(退出码 0),且输出的 JSON 里包含上下文字段,这段内容就会在下一轮推理前作为一条特殊消息送进模型视野。模型能看到,界面上不一定会完整展示给用户。这是给模型"附加说明"的机制。
第二种——阻止操作:以退出码 2 退出(或 JSON 里明确标注"拒绝"),正在发生的工具调用立刻被取消,工具不会执行,模型收到一条操作被拒绝的通知。这是 PreToolUse hook 拦截危险操作的主要手段,比如阻止 Claude 删除生产环境的数据。
第三种——报错:以其他非零退出码退出,系统把错误信息展示给用户,但不送进模型上下文,模型感知不到脚本出了什么问题。
这里有一点容易踩坑:直接打印文本永远不会进入模型的上下文,系统只把它写进日志。脚本里写了再多 echo 或 print,模型都看不到。想向模型传递信息,必须输出 JSON 并把内容放在对应的上下文字段里。
27 种 Hook 事件
系统覆盖了会话生命周期里几乎所有值得介入的节点,共 27 种:
| 事件 | 触发时机 |
|---|---|
| SessionStart | 会话启动时,最先触发的事件 |
| Setup | 初始化阶段,用于配置会话环境 |
| SessionEnd | 会话结束时(执行 /clear、退出登录等情况) |
| UserPromptSubmit | 用户提交一条消息时,在模型看到之前 |
| PreToolUse | 每次工具调用执行之前,此时还可以阻止 |
| PostToolUse | 工具调用成功完成之后 |
| PostToolUseFailure | 工具调用失败之后(报错、超时等) |
| Stop | 模型结束本轮推理、不再调用工具时 |
| StopFailure | 模型本轮推理异常终止时 |
| SubagentStart | 一个子 agent 启动时 |
| SubagentStop | 一个子 agent 完成任务时 |
| PermissionRequest | Claude 请求用户授权某项操作时 |
| PermissionDenied | 某项操作的权限被拒绝时 |
| PreCompact | 执行 /compact 压缩历史之前 |
| PostCompact | /compact 完成之后 |
| InstructionsLoaded | 指令文件(CLAUDE.md、rules 等)被加载时 |
| CwdChanged | 工作目录切换时 |
| FileChanged | 被监视的文件发生变化时(需先在其他 hook 里注册监视路径) |
| WorktreeCreate | 创建一个新的 git worktree 时 |
| WorktreeRemove | 删除一个 git worktree 时 |
| TaskCreated | 创建一个新任务时 |
| TaskCompleted | 一个任务完成时 |
| TeammateIdle | 协作模式下,某个协作者 agent 进入空闲状态时 |
| Notification | Claude 向用户发送通知时 |
| Elicitation | Claude 向用户请求补充信息时 |
| ElicitationResult | 上述请求得到用户回应后 |
| ConfigChange | 配置发生变化时 |
能力边界因时机而异

工具调用前的 hook 可以阻止操作——事情还没发生,还有机会叫停。工具调用后的 hook 只能附加信息,副作用已经落地,无法撤回。Stop 类 hook 在模型结束本轮推理之后才触发,适合做收尾工作、发通知,或者在特定条件下让模型再跑一轮。
超时
命令类 hook 默认允许运行 10 分钟,agent 类默认 60 秒。每条 hook 可以在配置里单独覆盖这个时间。
如果您认为内容不错,还可以订阅本合集,更快获取后续更新。
还可以加入矢量起源Agent交流群,与大家一起讨论~
更多推荐



所有评论(0)