Skip to content

Hooks:ユーザーフック実行チェーン

源码版本v4.0.10

役割

Cline の hooks システムは、主 agent loop の複数のライフポイント (prompt 送信前、ツール使用前、ツール使用後、タスク開始/終了、通知送信、圧縮前) でユーザー独自のシェルスクリプトを差し込めるようにする。スクリプトは stdin で構造化 JSON 入力を受け取り、stdout で JSON を返して Cline の挙動を制御できる。最も重要なのは cancel: true で直前のアクションを阻止できる点と、contextModification で対話に追加コンテキストを差し込める点である。全体は 3 層からなる。HookExecutor がエントリとしてディスパッチし (executeHook:58)、HookFactory + HookRunner が具体スクリプトの発見と実行を担い (HookRunner exec:289)、HookProcess が底层の子プロセスを管理する (HookProcess:89)。

これは Task ライフサイクル全体にまたがる。PreToolUse / PostToolUse は ToolExecutor がツール実行前後に呼び (PreToolUse executeHook:73)、UserPromptSubmit / TaskStart / TaskResume / TaskCancel は主 Task が対応ライフポイントで呼ぶ (UserPromptSubmit:1214)。Notification は NotificationHook モジュールが一元送出する (emitNotificationHook:50)。hook の「ブロック可能」セマンティクスはキャンセル可能点でのみ有効で、Notification のような fire-and-forget の hook は cancel を返しても無視される。

設計動機

  • 9 種の標準 hook:PreToolUse / PostToolUse / UserPromptSubmit / TaskStart / TaskResume / TaskCancel / TaskComplete / Notification / PreCompact (Hooks interface:102) で、主要ライフポイントを網羅。
  • JSON in / JSON out:hook スクリプトが受け取る stdin は clineVersion / hookName / timestamp / workspaceRoots / userId などのメタデータ付き JSON (completeParams:198) で、出力も JSON。
  • 3 種の返却フィールド:cancel はアクションをブロック、contextModification はコンテキスト追加、errorMessage はエラー説明を添付 (HookExecutionResult:33)。
  • cancellable フラグ:呼び出し元がこの hook でアクションをキャンセルできるかを決める。NotificationisCancellable: false (not cancellable:61)、PreToolUse は true。
  • プロセス隔離 + レジストリ:HookProcessRegistry が全アクティブ hook プロセスを追跡し、terminateAll が task キャンセル時に一括 kill する (HookProcessRegistry:17)。
  • クロスプラットフォームランチャー:getHookLaunchConfig は Windows PowerShell と Unix shebang を切り分け (getHookLaunchConfig:44)、ユーザーが .sh / .ps1 / .js いずれでも実行できるようにする。
  • stdout ストリーミングエコー:HookProcess は stdout/stderr を行単位で emit し、streamCallback[source stream path] プレフィックスを付けて UI にエコーする (streamCallback:124)。長時間 hook でも進捗が見える。
  • JSON 抽出のフォールバック:hook スクリプトが JSON の前後にデバッグログを混ぜて出力した場合、後ろから括弧の対応を走査して最後の完全な JSON オブジェクトを取り出す (JSON extraction:379)。

主要ファイル

データフロー

PreToolUse を例に取る。これが最も完全な「ブロック可能」チェーンである。ToolExecutor は実際にツールを実行する前に ToolHookUtils.executePreToolUseHook を呼ぶ:

typescript
// apps/vscode/src/core/task/tools/utils/ToolHookUtils.ts
const { executeHook } = await import("@core/hooks/hook-executor")

const pendingToolInfo: any = { tool: block.name }
if (block.params.path) pendingToolInfo.path = block.params.path
if (block.params.command) pendingToolInfo.command = block.params.command
// ...

const preToolResult = await executeHook({
  hookName: "PreToolUse",
  hookInput: {
    preToolUse: { toolName: block.name, parameters: block.params },
  },
  isCancellable: true,
  say: config.callbacks.say,
  setActiveHookExecution: config.callbacks.setActiveHookExecution,
  clearActiveHookExecution: config.callbacks.clearActiveHookExecution,
  messageStateHandler: config.messageState,
  taskId: config.taskId,
  hooksEnabled,
  model: getHookModelContext(config.api, config.services.stateManager),
  toolName: block.name,
  pendingToolInfo,
})

if (preToolResult.cancel === true) {
  await config.callbacks.clearActiveHookExecution()
  await config.callbacks.cancelTask()
  throw new PreToolUseHookCancellationError(preToolResult.errorMessage || "PreToolUse hook requested cancellation")
}

if (preToolResult.contextModification) {
  ToolHookUtils.addHookContextToConversation(config, preToolResult.contextModification, "PreToolUse")
}

この処理は PreToolUse caller:73 付近にある。executeHook に入ったらまず hooksEnabled を見 (early return:72)、オフならそのまま返す。次に HookFactory.hasHook でこの hook 名にスクリプトが設定されているか確認 (hasHook:80)。未設定でも即返す。設定されていれば HookProcess を作って子プロセスを spawn する:

typescript
// apps/vscode/src/core/hooks/HookProcess.ts
this.childProcess = spawn(launchConfig.command, launchConfig.args, {
  stdio: ["pipe", "pipe", "pipe"],
  shell: launchConfig.shell,
  detached: launchConfig.detached,
  cwd: this.cwd,
  windowsHide: true,
})

this.timeoutHandle = setTimeout(() => {
  if (this.childProcess && !this.isCompleted) {
    this.childProcess.kill("SIGTERM")
    reject(new Error(`Hook execution timed out after ${this.timeoutMs}ms.`))
  }
}, this.timeoutMs)

this.childProcess.stdout?.on("data", (data) => {
  this.stdoutBuffer += output
  this.handleOutput(output, didEmitEmptyLine, "stdout")
})
// ...
this.childProcess.on("close", (code, signal) => {
  this.exitCode = code
  // resolve / reject
})

this.childProcess.stdin?.write(inputJson)
this.childProcess.stdin?.end()

この処理は spawn child:176 付近にある。子プロセス終了後、HookRunner.exec が stdout を parseJsonOutput に渡す (parseJsonOutput:349)。合法 JSON があれば JSON を優先 (exitCode が非 0 でも優先) (JSON priority:454)。なければ exitCode で判断する。cancel: trueHookExecutor.executeHook まで伝播し、ToolHookUtils が受け取ると PreToolUseHookCancellationError を投げて cancelTask を呼ぶ。

境界と失敗

  • PreToolUse cancel は task 全体を abort:単にこのツールをスキップするだけでなく、cancelTask で agent loop 全体を止める (cancelTask on cancel:100)。
  • PostToolUse cancel は task を abort しない:runPostToolUseHook は cancel 受信時も say error して true を返すだけ。ツールは実行済みでロールバックできない (post cancel soft:494)。
  • Notification hook は cancel と contextModification を無視:この種の hook は一方向通知で、出力フィールドは明示的に無視される (ignore unsupported output:70)。
  • abort signal は即 reject:HookProcess は abortSignal を監視し、abort されたら即座に子プロセスを kill して reject する。close を待たない (abortHandler:139)。
  • タイムアウトはスクリプトパスでエラー:timeout エラーメッセージは this.scriptPath とタイムアウトミリ秒を含み、ユーザーがどの hook が固まったかを特定しやすい (timeout error:192)。
  • 出力は 1MB 上限:MAX_HOOK_OUTPUT_SIZE が無限出力をブロックし、超過すれば切り詰め通知を emit (output truncation:311)。
  • contextModification の切り詰め:MAX_CONTEXT_MODIFICATION_SIZE が hook による過大コンテキスト注入を防ぎ、超過すれば切り詰めて通知を付ける (context truncation:363)。
  • PreToolUse は attempt_completion をスキップ:attempt_completion は終了ツールで、PreToolUse ブロックを経ない (skip attempt_completion:30)。
  • NoOp hook は proto デフォルトを返す:スクリプトが見つからない hook は cancel: false / contextModification: "" / errorMessage: "" を返し、executor はこれを正常 no-op とみなす (NoOp runner:238)。
  • JSON 抽出は右から左へ走査:hook スクリプトが前にデバッグログを出して初回 JSON.parse が失敗した時、parseJsonOutput は括弧の対応で最後の完全なオブジェクトを探す (brace scan:385)。このフォールバックが重要。

まとめ

Hooks システムは Cline がユーザーのために残した「agent loop の主要点にスクリプトを差し込む」拡張ポイントである。主 Task がどのタイミングで hook を発火させるかは agent-loop/task-class、ツール実行器が PreToolUse / PostToolUse をどう直列化するかは cap-tools、StateManager が hook に workspaceRoots などのメタデータをどう提供するかは state-manager を参照のこと。

公式資料: Cline 文档 · README