首页
/ Cline Hooks 系统实战指南:文件钩子与运行时钩子拦截 Agent 全生命周期

Cline Hooks 系统实战指南:文件钩子与运行时钩子拦截 Agent 全生命周期

2026-09-06 16:36:55作者:滕妙奇

Cline 的钩子(Hooks)系统是拦截和增强自主编码代理行为的两大扩展点:文件钩子(File Hooks)通过 .cline/hooks/ 目录下的外部脚本(Bash/Python/TypeScript)以 JSON 负载通信,运行时钩子(Runtime Hooks)则以类型化的进程内回调(beforeRunbeforeModelafterTool 等)供插件直接操作运行时状态。读完本文,你将能够:按事件命名放置钩子脚本实现工具调用审计、破坏性操作拦截、上下文注入与参数改写;读懂钩子的 stdin/stdout JSON 协议;并理解文件钩子如何在源码层映射到运行时钩子回调,以及何时该改用 beforeModelregisterMessageBuilder() 插件方案。

术语定义:先分清三种"Hook"

参考文档 sdk/examples/hooks/README.md 要求在整个生态中统一使用以下术语:

  • 运行时钩子(Runtime hooks):类型化的进程内插件/代理生命周期回调,如 beforeRunbeforeModelafterTool
  • 文件钩子(File hooks):从钩子配置目录中被发现、以序列化 JSON 负载运行的外部脚本;
  • 钩子事件(Hook events):文件钩子使用的序列化负载名称,如 agent_endtool_callprompt_submit

从源码结构看,文件钩子本质上是运行时钩子层之上的适配器:核心运行时发现钩子文件、把事件名映射到运行时钩子回调,然后把匹配的脚本当作子进程执行并向其 stdin 写入 JSON 负载。这一关系由 hook-file-hooks.ts 中的 createHookConfigFileHooks() 实现——它返回一个标准的 AgentHooks 对象(含 beforeRun/beforeTool/afterTool/afterRun/onEvent),再通过 createHookConfigFileExtension() 注册为名为 core.hook_config_files 的运行时扩展,从而与插件钩子共用同一套合并逻辑(mergeAgentHooks)。

选型建议(原文档明确)

  • 想要工作区或用户级配置的 shell/Python 脚本 → 用文件钩子
  • 编写插件、需要类型化地访问运行时状态或影响模型/工具执行 → 用运行时钩子

另外注意执行粒度:beforeRunafterRun 包裹一次运行时 run()continue() 调用,在交互式会话中即"一次提交的用户轮次"。afterRun 对 completed、aborted、failed 三种结果都会触发;如果你只关心成功完成,请检查 result.status。文件钩子侧,成功完成对应 agent_end 事件;插件侧则使用 afterRun 并判断 result.status === "completed"

文件钩子事件与运行时钩子的映射表

完整继承原文档的映射关系(与 hook-file-config.tsHOOK_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_starthook-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_startprompt_submitagent_end 等)以 detached 方式异步派发,不阻塞主流程;
  • 多个同事件钩子的输出会合并mergeHookControls):cancel/review 任一为 true 即生效,context 以换行拼接,overrideInput 后写的优先。

钩子发现机制:目录、命名与解释器推断

搜索目录

paths.ts#L487-L501resolveHooksConfigSearchPaths() 定义了钩子目录的发现顺序(去重后):

  1. 用户级:Documents 下的 Cline/Hooks 目录(resolveDocumentsExtensionPath("Hooks"));
  2. 用户级:~/.cline/hooks
  3. 工作区级(旧路径,标记 deprecated):<workspace>/.config/cline/hooks
  4. 工作区级(当前路径):<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。合法的事件基名即上表中的 TaskStartPreToolUse 等十个名字。

解释器推断

inferHookCommand()hook-file-hooks.ts#L327-L370)负责为每个钩子文件构造执行命令,优先级为:

  1. Shebang 优先:解析首行 #!,并归一化解释器(如 python3 在 Windows 上改写为 py -3,且当 py 命令缺失时会回退到 python,见 getWindowsPythonFallbackCommand);
  2. 按扩展名回退.sh/.bash/.zshbash.js/.mjs/.cjsnode.ts/.mts/.ctsbun run.pypython3(Windows 为 py -3);.ps1pwsh/powershell -File
  3. 无扩展名默认 bash

也就是说,文档示例中 chmod +x 与 shebang 主要服务于可移植性;即使脚本不可执行,核心也会用推断出的解释器命令数组来 spawn。若确实遇到 EACCESsubprocess-runner.ts#L60-L76 会给出明确的错误提示。

钩子输入负载:stdin 上的 JSON

所有钩子都会从 stdin 收到一份详细 JSON 事件,核心字段由 basePayload()/createPayloadBase() 构造(subprocess.ts#L251-L280):clineVersionhookNametimestamptaskIdworkspaceRootsuserId,以及从源码可确认的额外字段 sessionContext(含 rootSessionId)、workspaceInfo(会话启动时生成的结构化 git/路径元数据,让钩子不必自己跑 git 命令)、agent_idparent_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 计数与 turnoutputTextstatus);agent_error 包含 errorname/message/stack);agent_abortsession_shutdown 携带 reasonprompt_submit 携带 userPromptSubmit.prompt。这些字段结构均可在 hook-file-hooks.ts 的各 runXxx 函数中对照核实。

钩子输出:stdout 上的控制 JSON

钩子必须在 stdout 返回 JSON 对象,空 {} 表示"什么都不做"。可用控制字段:

字段 类型 效果 生效事件
cancel boolean 取消待执行的工具调用 PreToolUse
review boolean 暂停并请求用户审查 PreToolUse
context string 向 Agent 下一轮注入上下文 PreToolUsePostToolUse
errorMessage string 向 Agent 暴露一条错误 PreToolUse
overrideInput object 执行前替换工具输入 PreToolUse

这些字段的解析逻辑值得展开(subprocess.ts#L74-L83HookOutputSchematoHookControl):

  • 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 outDEFAULT_TOOL_HOOK_TIMEOUT_MSsubprocess.ts#L128-L131)。

cancel/context/overrideInput 最终如何影响运行时,可在 hook-file-hooks.ts#L536-L584beforeToolResultFromControl/afterToolResultFromControl 中对照:cancelstop(+reason),contextappendContextoverrideInputinput

官方示例清单: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

快速上手三步

  1. 把钩子拷到项目:文件钩子放入 .cline/hooks/(或 ~/.cline/hooks--hooks-dir 指定目录),且文件名必须等于事件名;
  2. 赋予执行权限chmod +x .cline/hooks/PreToolUse.*
  3. 测试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-calltool-resultreasoningimagefile 等运行时 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() 插件方案。

登录后查看全文
热门项目推荐
相关项目推荐