从源码泄露看 Claude Code 的 Harness 工程:如何给 AI 编码套上缰绳
当你让 Claude Code”随便改改这个项目”的时候,AI 可能会:
-
改了 30 个文件,其中 28 个你不需要改 -
重写了核心模块,用了一个你不知道的依赖 -
跑了一堆测试,把你的 CI 搞红了 -
commit 了 47 个碎片化的 commit,理由都是”fix” -
Harness 的本质,是给 AI Coding 套上边界:让它知道什么能做,什么不能做,在什么范围内做,做完了交给谁来 review。 -
Claude Code 的 Harness 系统有四个维度,工具层、Agent 层、权限层、生命周期层,层层叠加,形成完整的安全体系。
Claude Code 的每一个能力,都以”工具”的形式暴露给 AI。工具层 Harness,做的就是这件事:控制 AI 手里有哪些工具可用。 工具注册表机制 所有工具在 tools.ts 中注册,通过 getAllBaseTools() 统一输出:
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
GlobTool,
GrepTool,
FileReadTool,
FileEditTool,
// ... 40+ 个工具
];
}
这个注册表是唯一的真相来源。工具的增删改都在这里,不会有任何隐藏的能力。 工具池组装(Tool Pool) 每个 Agent 调用工具时,通过 assembleToolPool() 动态组装工具池:
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext);
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext);
// 内置工具在前,MCP 工具在后
// 顺序稳定:服务端有缓存断点,顺序一变缓存全部失效
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
);
}
这里有个很重要的设计细节:工具的注册顺序是固定的。服务端 API 有一个全局缓存断点,缓存 key 依赖于工具列表的稳定顺序。如果每次请求工具列表顺序不同,缓存就会失效——Claude Code 严格维护这个顺序,就是为了让 API 缓存效率最优。 条件加载与 Feature Flag 工具系统还支持按 Feature Flag 条件加载:
// 如果 COORDINATOR_MODE 关闭,这个模块不会被编译进产物
const coordinatorModule = feature('COORDINATOR_MODE')
? require('./coordinator/coordinatorMode.js')
: null;
这是编译时死代码消除,不是运行时 if-else——打包产物里不会有无用的代码。
📌 图1:工具层Harness架构

有了工具,还要决定 AI 在什么权限级别下使用这些工具。权限层 Harness 回答的就是这个问题。 四种权限模式 Claude Code 定义了四种权限模式:
type PermissionMode =
| 'default' // 用户逐个审批
| 'plan' // Plan 模式下自动审批
| 'bypassPermissions' // 完全绕过(危险!)
| 'auto' // 基于 AI 判断自动审批
用户可以在 Claude Code 的安全设置里切换这个模式,但 bypassPermissions 只有在明确知晓风险的情况下才会触发。 每次工具调用都经过权限检查,由 useCanUseTool 这个 React Hook 驱动:
📌 图2:权限检查流程

// bashSecurity.ts — 危险命令模式检测
// 通过 Set 存储危险命令 baseCmd,不做简单字符串匹配
ZSH_DANGEROUS_COMMANDS.has(baseCmd)
// bashPermissions.ts — 前缀匹配检查
// 例如:nice rm -rf / 前缀是 "nice",不是 "rm"
// 但语义上 rm -rf / 经过包装器仍然执行删除操作
// pathValidation.ts — 路径越界检查
// 原理:解析命令 AST,找到真实可写的路径
// 与用户指定的 baseDir 比对
// 防止 ../../../etc/passwd 这类路径逃逸
这是最常被讨论的一层——如何控制子 Agent 的行为边界。 Claude Code 提供了三层 Agent 架构,从轻到重: 第一层:Fork Subagent(隐式分叉) 调用 Agent Tool 但不指定 subagent_type 时,触发隐式分叉:
export const FORK_AGENT = {
agentType: 'fork',
tools: ['*'], // 继承父 Agent 的精确工具池
permissionMode: 'bubble', // 权限弹窗回到父 Terminal
model: 'inherit', // 继承父 Agent 的模型
};
Fork 最精妙的设计:不重新生成 system prompt。子 Agent 直接继承父 Agent 渲染好的字节,避免了 GrowthBook 冷热状态切换导致的 prompt 内容变化,也避免了 API 端缓存失效。 第二层:Built-in Agents(内置专用 Agent) Claude Code 内置了 5 种专用 Agent:
|
|
|
|
|
|---|---|---|---|
general-purpose |
|
|
|
explore |
|
|
|
plan |
|
|
|
verification |
|
|
|
claude-code-guide |
|
|
|
Explore Agent 的 prompt 明确写着:
=== CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
This is a READ-ONLY exploration task.
You are STRICTLY PROHIBITED from:
- Creating new files
- Modifying existing files
- Deleting files
Verification Agent 更是把”破坏性验证”作为核心目标——它的 prompt 里总结了人类测试者会犯的两类错误:验证逃避(跳过没测就说 PASS)和被前 80% 迷惑(看到 UI 精美就放行)。 当 CLAUDE_CODE_COORDINATOR_MODE=1 时,启用完全自主的多 Agent 协调:
📌 图3:三层次Agent架构

const INTERNAL_WORKER_TOOLS = new Set([
'TeamCreate', // 派发更多 Worker
'TeamDelete', // 删除 Worker
'SendMessage', // 发消息
'SyntheticOutput', // 输出结构化结果
]);
Workers 完全无法操作用户的文件系统——这是最极端的权限隔离。
📌 图4:Coordinator Mode多Agent编排
Harness 不仅存在于运行时,还存在于整个 Claude Code 的生命周期里。这层的核心是 Hook 系统。 五大生命周期 Hooks Claude Code 定义了五个标准 Hook 事件:
export const HOOK_EVENTS = [
'PostToolUse', // 工具执行后触发
'UserPromptSubmit', // 用户提交提示后触发
'SessionStart', // 会话开始时触发
'SessionEnd', // 会话结束时触发
'Stop', // 用户中断时触发
];
每个 Hook 事件都可以挂载任意数量的处理程序,处理程序可以是命令(Shell 脚本)或函数(TypeScript 回调):
// 两种 Hook 类型
type FunctionHook = {
type: 'function';
callback: (messages, signal) => boolean | Promise<boolean>;
timeout?: number;
errorMessage: string;
};
// 注册 Hook
addSessionHook(
setAppState,
sessionId,
'SessionStart', // 事件类型
'before/*', // matcher(glob 模式)
{ type: 'command', command: 'echo hello' },
);
Hook 的两个关键特性
- Session-scoped
:Hook 是会话级别的,会话结束自动清除,不会污染下一个会话 - 并发安全
:使用 Map而非Record存储,避免并发写入时的 O(N²) 性能问题
// 并发优化:parallel() 触发 N 个 addFunctionHook 时
// Map.set() 是 O(1),返回 prev 意味着零监听器触发
// 如果用 Record + spread,每次调用 O(N),N 次就是 O(N²)
export type SessionHooksState = Map<string, SessionStore>;
-
📌 图5:Hook生命周期系统

这是信息安全最基础的原则:每个进程、每个模块,只能访问完成其任务所必需的最少权限。 Claude Code 在每一层都严格遵循:
- 工具层
:子 Agent 继承父 Agent 的精确工具池,不多也不少 - 权限层
:Explore/Plan Agent 被移除写工具,最小权限集合用 prompt + 工具移除双重锁定 - Agent 层
:Workers 只有 INTERNAL_WORKER_TOOLS,根本无法访问文件系统
权限不是通过 ACL 列表分配的,而是通过持有”令牌”来体现。 Claude Code 的 Fork 设计就是能力模型的体现:子 Agent 持有父 Agent 发行的”能力令牌”——渲染好的 system prompt 字节。孩子凭这个字节在授权范围内行事,无法突破边界。
Worktree 隔离是 Git 提供的能力,Claude Code 拿来做 Agent 隔离:一个 Worktree 就是一个轻量级 Git 目录副本,对主仓库的任何操作不影响 Worktree 里的代码,反之亦然。
Coordinator 模式本质上是软件工程里经典的”监督控制”模式,与 Erlang/OTP 的 Actor 模型有相似之处:Actor 之间通过消息传递通信,Supervisor 负责监控生命周期。Claude Code 的 Coordinator 通过 接收 Workers 的消息,通过 SendMessageTool 发指令——这是一个消息驱动的监督控制架构。
任务:把这个 Django 项目迁移到 FastAPI
不用 Harness:
Claude Code 一个人改,改了 200 个文件,你不知道它做到哪了
用 Coordinator Mode:
1. Plan Agent 制定重构计划(只读)
2. Coordinator 把任务分解成模块迁移、路由迁移、测试迁移
3. 多个 Worker 并行处理不同模块(各自独立 Git 分支)
4. Coordinator 汇总结果,发现冲突
5. 你来 review 每个模块的改动
任务:审查这 30 个 PR
用 Explore + Verification:
→ Explore Agent 快速并行搜索关键代码路径
→ 发现可疑模式
→ Verification Agent 做破坏性验证
→ 输出结构化审查报告
任务:修了一个 bug,还想看看有没有同类问题
用 Fork Subagent:
→ 子 Agent 继承当前上下文
→ 在独立分支上探索同类模式
→ 找到 3 个同类问题,一并修复
→ 结果异步返回
原则一:隔离是默认设置 每个 Agent 的工具池、上下文、运行环境都是被明确隔离的。你不需要担心一个 Agent 改的文件会影响另一个。 原则二:只读 Agent 是免费的保险 Explore、Plan、Verification 这三个只读 Agent,本质上是给 AI Coding 上了三重保险——做事之前先想清楚(Plan),做完了验证(Verification),做的时候知道代码库里有什么(Explore)。 原则三:渐进式复杂度 从 Fork Subagent → Built-in Agents → Coordinator Mode,复杂度和自主性是递增的。Claude Code 没有强迫你一开始就上 Coordinator,而是让你从最简单的场景开始,逐步升级。 原则四:多层叠加,既灵活又安全 Fork(Capability) + Coordinator(Supervisor) + INTERNAL_WORKER_TOOLS(Capability 限制),三层叠加——既保证了灵活性(Fork 可以做父 Agent 授权范围内的任何事),又保证了安全性(Coordinator 可以在 Workers 越界时直接 kill 掉)。
相关链接:
-
源码(泄露镜像):https://github.com/instructkr/claude-code
夜雨聆风