Cline Hooks 系统实战指南:文件钩子与运行时钩子拦截 Agent 全生命周期
Cline 的钩子(Hooks)系统是拦截和增强自主编码代理行为的两大扩展点:文件钩子(File Hooks)通过 .cline/hooks/ 目录下的外部脚本(Bash/Python/TypeScript)以 JSON 负载通信,运行时钩子(Runtime Hooks)则以类型化的进程内回调(beforeRun、beforeModel、afterTool 等)供插件直接操作运行时状态。读完本文,你将能够:按事件命名放置钩子脚本实现工具调用审计、破坏性操作拦截、上下文注入与参数改写;读懂钩子的 stdin/stdout JSON 协议;并理解文件钩子如何在源码层映射到运行时钩子回调,以及何时该改用 beforeModel 或 registerMessageBuilder() 插件方案。
术语定义:先分清三种"Hook"
参考文档 sdk/examples/hooks/README.md 要求在整个生态中统一使用以下术语:
- 运行时钩子(Runtime hooks):类型化的进程内插件/代理生命周期回调,如
beforeRun、beforeModel、afterTool; - 文件钩子(File hooks):从钩子配置目录中被发现、以序列化 JSON 负载运行的外部脚本;
- 钩子事件(Hook events):文件钩子使用的序列化负载名称,如
agent_end、tool_call、prompt_submit。
从源码结构看,文件钩子本质上是运行时钩子层之上的适配器:核心运行时发现钩子文件、把事件名映射到运行时钩子回调,然后把匹配的脚本当作子进程执行并向其 stdin 写入 JSON 负载。这一关系由 hook-file-hooks.ts 中的 createHookConfigFileHooks() 实现——它返回一个标准的 AgentHooks 对象(含 beforeRun/beforeTool/afterTool/afterRun/onEvent),再通过 createHookConfigFileExtension() 注册为名为 core.hook_config_files 的运行时扩展,从而与插件钩子共用同一套合并逻辑(mergeAgentHooks)。
选型建议(原文档明确):
- 想要工作区或用户级配置的 shell/Python 脚本 → 用文件钩子;
- 编写插件、需要类型化地访问运行时状态或影响模型/工具执行 → 用运行时钩子。
另外注意执行粒度:beforeRun 与 afterRun 包裹一次运行时 run() 或 continue() 调用,在交互式会话中即"一次提交的用户轮次"。afterRun 对 completed、aborted、failed 三种结果都会触发;如果你只关心成功完成,请检查 result.status。文件钩子侧,成功完成对应 agent_end 事件;插件侧则使用 afterRun 并判断 result.status === "completed"。
文件钩子事件与运行时钩子的映射表
完整继承原文档的映射关系(与 hook-file-config.ts 中 HOOK_CONFIG_FILE_EVENT_MAP 常量一致):
| 文件钩子文件名 | 文件钩子事件 | 背后的运行时钩子 |
|---|---|---|
TaskStart |
agent_start |
beforeRun |
TaskResume |
agent_resume |
beforeRun(带 resume 上下文) |
UserPromptSubmit |
prompt_submit |
beforeRun 加提交的 prompt 上下文 |
PreToolUse |
tool_call |
beforeTool |
PostToolUse |
tool_result |
afterTool |
TaskComplete |
agent_end |
完成时的 afterRun |
TaskError |
agent_error |
失败时的 afterRun |
TaskCancel |
agent_abort |
带取消/中止原因的 afterRun 或会话关闭 |
SessionShutdown |
session_shutdown |
会话清理/运行时关闭 |
PreCompact |
当前未为文件钩子接线 | 无 |
PreCompact 在映射表中被显式置为 undefined(见 hook-file-config.ts#L41),即该文件会被识别但不会触发任何事件——压缩场景请走运行时钩子或插件方案。
源码中几个值得注意的行为细节:
agent_resume的判定:beforeRun触发时检查环境变量CLINE_HOOK_AGENT_RESUME === "1",是则派发agent_resume负载,否则派发agent_start(hook-file-hooks.ts#L927-L934);agent_end仅在result.status === "completed"时派发(hook-file-hooks.ts#L962-L976),abort(错误消息含 cancel/abort/interrupt 字样)走agent_abort,其余错误走agent_error;- 工具类钩子(
tool_call/tool_result)是阻塞执行,超时默认 120 秒(toolCallTimeoutMs ?? 120000);而生命周期类钩子(agent_start、prompt_submit、agent_end等)以 detached 方式异步派发,不阻塞主流程; - 多个同事件钩子的输出会合并(
mergeHookControls):cancel/review任一为true即生效,context以换行拼接,overrideInput后写的优先。
钩子发现机制:目录、命名与解释器推断
搜索目录
paths.ts#L487-L501 中 resolveHooksConfigSearchPaths() 定义了钩子目录的发现顺序(去重后):
- 用户级:
Documents下的 Cline/Hooks 目录(resolveDocumentsExtensionPath("Hooks")); - 用户级:
~/.cline/hooks; - 工作区级(旧路径,标记 deprecated):
<workspace>/.config/cline/hooks; - 工作区级(当前路径):
<workspace>/.cline/hooks。
此外,CLI 还提供 --hooks-dir <path> 选项用于从其他目录加载钩子(program.ts#L72-L75,默认 ~/.cline/hooks),例如 CI 场景可用 cline --hooks-dir ./ci/hooks -i "test prompt"。
文件命名与扩展名
钩子文件必须按所处理的事件命名,文件名匹配不区分大小写,支持的扩展名在 hook-file-config.ts#L49-L62 中枚举:无扩展名(legacy)、.sh、.bash、.zsh、.js、.mjs、.cjs、.ts、.mts、.cts、.py、.ps1。合法的事件基名即上表中的 TaskStart、PreToolUse 等十个名字。
解释器推断
inferHookCommand()(hook-file-hooks.ts#L327-L370)负责为每个钩子文件构造执行命令,优先级为:
- Shebang 优先:解析首行
#!,并归一化解释器(如python3在 Windows 上改写为py -3,且当py命令缺失时会回退到python,见getWindowsPythonFallbackCommand); - 按扩展名回退:
.sh/.bash/.zsh→bash;.js/.mjs/.cjs→node;.ts/.mts/.cts→bun run;.py→python3(Windows 为py -3);.ps1→pwsh/powershell -File; - 无扩展名默认
bash。
也就是说,文档示例中 chmod +x 与 shebang 主要服务于可移植性;即使脚本不可执行,核心也会用推断出的解释器命令数组来 spawn。若确实遇到 EACCES,subprocess-runner.ts#L60-L76 会给出明确的错误提示。
钩子输入负载:stdin 上的 JSON
所有钩子都会从 stdin 收到一份详细 JSON 事件,核心字段由 basePayload()/createPayloadBase() 构造(subprocess.ts#L251-L280):clineVersion、hookName、timestamp、taskId、workspaceRoots、userId,以及从源码可确认的额外字段 sessionContext(含 rootSessionId)、workspaceInfo(会话启动时生成的结构化 git/路径元数据,让钩子不必自己跑 git 命令)、agent_id、parent_agent_id。
PreToolUse(tool_call)事件:
{
"hookName": "tool_call",
"clineVersion": "1.0.0",
"timestamp": "2026-01-15T10:30:00Z",
"taskId": "conv-123",
"workspaceRoots": ["/path/to/repo"],
"userId": "user",
"iteration": 1,
"tool_call": {
"id": "call-456",
"name": "read_files",
"input": {"filePath": "/path/to/file.ts"}
}
}
除 tool_call 嵌套字段外,负载还带有兼容性的 preToolUse 段(toolName + 字符串化的 parameters),见 hook-file-hooks.ts#L787-L801。
PostToolUse(tool_result)事件:
{
"hookName": "tool_result",
"clineVersion": "1.0.0",
"timestamp": "2026-01-15T10:30:00Z",
"tool_result": {
"id": "call-456",
"name": "read_files",
"input": {"filePath": "/path/to/file.ts"},
"output": "file contents here",
"error": null,
"durationMs": 45
}
}
TaskStart 等其他生命周期事件:
{
"hookName": "agent_start",
"clineVersion": "1.0.0",
"timestamp": "2026-01-15T10:30:00Z",
"taskId": "conv-123",
"workspaceRoots": ["/path/to/repo"],
"userId": "user"
}
agent_end 负载会额外包含 iteration 计数与 turn(outputText、status);agent_error 包含 error(name/message/stack);agent_abort 与 session_shutdown 携带 reason;prompt_submit 携带 userPromptSubmit.prompt。这些字段结构均可在 hook-file-hooks.ts 的各 runXxx 函数中对照核实。
钩子输出:stdout 上的控制 JSON
钩子必须在 stdout 返回 JSON 对象,空 {} 表示"什么都不做"。可用控制字段:
| 字段 | 类型 | 效果 | 生效事件 |
|---|---|---|---|
cancel |
boolean | 取消待执行的工具调用 | PreToolUse |
review |
boolean | 暂停并请求用户审查 | PreToolUse |
context |
string | 向 Agent 下一轮注入上下文 | PreToolUse、PostToolUse |
errorMessage |
string | 向 Agent 暴露一条错误 | PreToolUse |
overrideInput |
object | 执行前替换工具输入 | PreToolUse |
这些字段的解析逻辑值得展开(subprocess.ts#L74-L83 的 HookOutputSchema 与 toHookControl):
context兼容旧字段contextModification:两者都是字符串时,优先取context;context有 50,000 字符上限(MAX_HOOK_CONTEXT_SIZE),超长会被截断并附加[hook context truncated]标记,防止钩子撑爆 prompt(subprocess.ts#L52-L65);cancel: true时消息不会被注入为对话上下文:errorMessage(或兜底的context)会作为取消原因(cancelReason)单独传递,避免一个钩子的注入上下文泄漏进另一个钩子的取消原因;- stdout 解析容错:runner 会先查找
HOOK_CONTROL\t前缀的行(取最后一条)作为控制 JSON,否则把整个 trim 后的 stdout 当 JSON 解析;解析失败会记录parseError并告警,但不会让 Agent 崩溃(subprocess-runner.ts#L29-L58)。这也是"日志请走 stderr、stdout 只放 JSON"这一约束的底层原因。 tool_call/tool_result钩子默认 120 秒超时,超时进程被SIGKILL,并记录hook command timed out(DEFAULT_TOOL_HOOK_TIMEOUT_MS,subprocess.ts#L128-L131)。
cancel/context/overrideInput 最终如何影响运行时,可在 hook-file-hooks.ts#L536-L584 的 beforeToolResultFromControl/afterToolResultFromControl 中对照:cancel → stop(+reason),context → appendContext,overrideInput → input。
官方示例清单:Bash / Python / TypeScript
sdk/examples/hooks/ 目录提供了覆盖各场景的可运行示例。文档中的复制命令以 sdk/ 为基准目录,从仓库根目录执行时需把路径写成 sdk/examples/hooks/...。
Bash 示例
PreToolUse.sh —— 记录每次工具调用及其输入,适合审计 Agent 将要做什么(参考实现见 PreToolUse.sh:读 stdin、jq 提取 tool_call.name 与参数,写 stderr,返回 {}):
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse.sh .cline/hooks/
chmod +x .cline/hooks/PreToolUse.sh
cline -i "do something" # 在 stderr 中看到工具调用日志
PostToolUse.sh —— 检查工具结果并追加补充上下文:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PostToolUse.sh .cline/hooks/
chmod +x .cline/hooks/PostToolUse.sh
cline -i "do something" # 看到工具结果被记录并增强
PreToolUse_BlockDestructive.sh —— 拦截 force push、批量删除等破坏性操作:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse_BlockDestructive.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
cline -i "clean up the repo" # 破坏性操作将被拦截
PreToolUse_RequireReview.sh —— 对关键文件的写入强制人工审查:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse_RequireReview.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
cline -i "update dependencies" # 关键文件写入会暂停等待审查
PreToolUse_InjectFileContext.sh —— 在执行前抽取并注入文件上下文(相关测试文件、lock 文件、环境信息):
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse_InjectFileContext.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
cline -i "review the configuration" # 相关文件会被自动提及
TaskStart.sh / TaskComplete.sh / SessionShutdown.sh —— 跟踪 Agent 会话生命周期(开始、结束、关闭):
mkdir -p .cline/hooks
cp sdk/examples/hooks/TaskStart.sh .cline/hooks/
cp sdk/examples/hooks/TaskComplete.sh .cline/hooks/
cp sdk/examples/hooks/SessionShutdown.sh .cline/hooks/
chmod +x .cline/hooks/Task*.sh .cline/hooks/SessionShutdown.sh
cline -i "do something" # 会话生命周期被记录
Python 示例
PreToolUse.py —— Python 版工具调用日志与过滤:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse.py .cline/hooks/
chmod +x .cline/hooks/PreToolUse.py
cline -i "do something" # Python 钩子记录工具调用
PostToolUse.py —— Python 版后置结果增强:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PostToolUse.py .cline/hooks/
chmod +x .cline/hooks/PostToolUse.py
cline -i "do something" # Python 钩子增强工具结果
PreToolUse_InjectContext.py —— Python 版上下文注入,含文件分析(测试文件、配置文件、lock 文件、Node.js 版本、git 分支):
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse_InjectContext.py .cline/hooks/PreToolUse.py
chmod +x .cline/hooks/PreToolUse.py
cline -i "add a new feature" # 相关文件与环境信息被注入
TypeScript 示例
PreToolUse.ts —— TypeScript 钩子,用于进阶的工具调用过滤与日志:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse.ts .cline/hooks/
chmod +x .cline/hooks/PreToolUse.ts
cline -i "do something" # TypeScript 钩子通过 bun 执行
PostToolUse.ts —— TypeScript 后置执行钩子:
mkdir -p .cline/hooks
cp sdk/examples/hooks/PostToolUse.ts .cline/hooks/
chmod +x .cline/hooks/PostToolUse.ts
cline -i "do something" # TypeScript 钩子通过 bun 执行
PreToolUse_ModifyInput.ts —— 执行前改写工具输入(路径归一化、补默认值、清洗):
mkdir -p .cline/hooks
cp sdk/examples/hooks/PreToolUse_ModifyInput.ts .cline/hooks/PreToolUse.ts
chmod +x .cline/hooks/PreToolUse.ts
cline -i "install dependencies" # npm install 自动加上 --save-exact
快速上手三步
- 把钩子拷到项目:文件钩子放入
.cline/hooks/(或~/.cline/hooks、--hooks-dir指定目录),且文件名必须等于事件名; - 赋予执行权限:
chmod +x .cline/hooks/PreToolUse.*; - 测试:
cline -i "test prompt",或指定目录cline --hooks-dir ./my-hooks -i "test prompt"。
常见钩子模式(可直接复制)
以下模式完整继承自原文档,均只依赖 jq/标准库,与上文协议一一对应。
1. 记录并放行(Bash)
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
echo "Action: $tool" >&2
echo '{}'
2. 向下一轮注入上下文
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
if [ "$tool" = "run_commands" ]; then
branch=$(git branch --show-current 2>/dev/null)
echo "{\"context\": \"Current branch: $branch\"}"
else
echo '{}'
fi
3. 执行前修改工具输入
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
file=$(echo "$input" | jq -r '.tool_call.input.filePath')
if [ "$tool" = "read_files" ] && [[ $file == ~/* ]]; then
normalized="${file/#\~/$HOME}"
echo "{\"overrideInput\": {\"filePath\": \"$normalized\"}}"
else
echo '{}'
fi
4. 拦截特定工具或命令
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
cmd=$(echo "$input" | jq -r '.tool_call.input.command // empty')
if [ "$tool" = "run_commands" ] && [[ $cmd =~ git\ push\ --force ]]; then
echo '{"cancel": true, "errorMessage": "Force push is blocked."}'
else
echo '{}'
fi
5. 对敏感文件要求审查
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
file=$(echo "$input" | jq -r '.tool_call.input.filePath // empty')
if ([ "$tool" = "editor" ] || [ "$tool" = "write_file" ]) && \
[[ $file =~ (package\.json|\.env|secrets|tsconfig) ]]; then
echo '{"review": true, "context": "This will modify a critical file"}'
else
echo '{}'
fi
6. Python:解析并操作 JSON
#!/usr/bin/env python3
import sys
import json
event = json.load(sys.stdin)
tool_name = event.get("tool_call", {}).get("name", "")
tool_input = event.get("tool_call", {}).get("input", {})
if tool_name == "read_files":
file_path = tool_input.get("filePath", "")
if file_path.endswith(".test.ts"):
print(json.dumps({"context": "This is a test file"}))
else:
print(json.dumps({}))
else:
print(json.dumps({}))
7. TypeScript:类型安全 + 异步操作
#!/usr/bin/env bun
interface HookEvent {
tool_call: { name: string; input: Record<string, unknown> };
}
const event: HookEvent = JSON.parse(await Bun.stdin.text());
const toolName = event.tool_call.name;
if (toolName === "run_commands") {
const branch = await getGitBranch();
console.log(JSON.stringify({ context: `Branch: ${branch}` }));
} else {
console.log(JSON.stringify({}));
}
async function getGitBranch(): Promise<string> {
return "main";
}
运行时钩子进阶:自定义压缩(Custom Compaction)
文件钩子只能"观察"生命周期事件;对消息压缩这类需要直接改写请求的高级场景,应使用 TypeScript 运行时钩子插件。仓库提供了完整示例 custom-compaction-hook.example.ts,它通过 hooks.beforeModel 估算请求体积,并在向模型供应商发起请求前把较旧的中间历史替换为一条摘要消息。原文档的安装与验证流程为:
cline plugin install <示例文件位置> --cwd .
cline -i "Search the codebase for dispatcher usage, then summarize it"
(原文档以仓库 URL 安装该示例;本地开发时对应文件即 sdk/examples/hooks/custom-compaction-hook.example.ts。)
两种压缩方案的取舍
| 示例 | 扩展点 | 消息形态 | 适用场景 |
|---|---|---|---|
custom-compaction-hook.example.ts(位于 .cline/plugins/) |
hooks.beforeModel 运行时钩子 |
Agent 运行时请求消息,含 tool-call、tool-result、reasoning、image、file 等运行时 part |
需要运行时钩子上下文、当前运行时快照或直接改写请求对象的场景 |
plugins/custom-compaction.ts(参考 sdk/examples/plugins/ 下的 custom-compaction.ts) |
api.registerMessageBuilder() |
运行时消息转换为 SDK/供应商绑定 Message[] 之后的形态 |
大多数可复用的、插件自有的消息改写与压缩策略 |
原文档的结论:普通插件自有的供应商消息改写优先用 registerMessageBuilder()——它在核心消息管线中运行、且先于内置的 provider-safety builder;只有当压缩逻辑需要运行时钩子上下文、或必须检查精确的运行时请求对象时,才使用 beforeModel。
调试钩子的三种手段
1. 打印钩子调用轨迹:
cline --verbose "your prompt"
2. 手动喂 JSON 测试单个钩子(无需启动 Agent):
echo '{"tool_call": {"name": "read_files", "input": {"filePath": "test.ts"}}}' | .cline/hooks/PreToolUse.sh
3. 检查钩子输出的 JSON 结构:
.cline/hooks/PreToolUse.sh < input.json | jq .
从源码看,手动测试之所以可行,是因为钩子子进程的全部契约就是"stdin 收 JSON、stdout 回 JSON"(subprocess-runner.ts#L126-L220)。另外,核心还会把审计负载以 JSONL 追加写入钩子日志(CLINE_HOOKS_LOG_PATH 或 ~/.cline 数据目录下的 hooks.jsonl,见 hook-file-hooks.ts#L598-L607),可用它回溯每一次事件的完整负载。
实战注意事项(原文档 Tips 全量整理)
--yolo模式下钩子被禁用——需使用--act或--plan模式启用钩子;- 日志一律写 stderr——stdout 被控制 JSON 独占;
- 保持钩子快——它们在任何一次工具调用前后都会执行,性能直接影响 Agent 吞吐(且工具钩子有 120 秒硬超时);
- 用
jq做 JSON 提取——JSON 解析容易出错,jq是最安全的提取方式; - 允许多钩子共存——不同事件文件可同时放在
.cline/hooks/,同一事件的多个文件其输出按上文合并规则叠加; - 自定义目录加载——
--hooks-dir ./ci/hooks可把钩子集中放在仓库内的 CI 专用目录,便于团队统一审计策略。
小结
Cline 钩子系统的设计可以概括为一句话:用统一的运行时钩子回调(beforeRun/beforeTool/afterTool/afterRun/onEvent)作为单一扩展内核,文件钩子只是把其中五个回调桥接成"按事件命名的外部脚本 + stdin/stdout JSON"的适配层。掌握 sdk/examples/hooks/README.md 中的事件映射表与输入输出协议,配合 sdk/packages/core/src/hooks/ 下的 hook-file-config.ts(发现与命名)、hook-file-hooks.ts(事件桥接与控制合并)、subprocess.ts(负载与控制字段)、subprocess-runner.ts(子进程执行与容错)四个文件,你就能从"照着示例拷脚本"进阶到"按团队审计规范定制拦截策略",并在需要改写模型请求时平滑切换到 beforeModel / registerMessageBuilder() 插件方案。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00