Documentation Index
Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
Use this file to discover all available pages before exploring further.
Hooks を使用すると、AI セッションの主要なライフサイクルポイントにカスタムロジックを注入でき、監査ログ、セキュリティ制御、コンテキスト注入、動的な動作変更が可能になります。
イベント概要
| イベント | トリガー | 制御可能な動作 |
|---|
PreToolUse | ツール呼び出し前 | インターセプト / 許可 / 入力の変更 |
PostToolUse | ツール成功後 | 監査 / コンテキスト注入 / 出力の上書き |
PostToolUseFailure | ツール失敗後 | エラーハンドリング / ロギング |
UserPromptSubmit | ユーザープロンプト送信前 | コンテキスト注入 / インターセプト |
SessionStart | セッション開始時 | 初期化 / コンテキスト注入 |
SessionEnd | セッション終了時 | クリーンアップ / ロギング |
Stop | AI が生成を停止した時 | 停止の防止、続行の強制 |
SubagentStart | サブエージェント開始時 | 監視 / ロギング |
SubagentStop | サブエージェント停止時 | 監視 / ロギング |
PreCompact | コンテキスト圧縮前 | 監視 / ロギング |
PostCompact | コンテキスト圧縮後 | 監視 / ロギング |
CwdChanged | 作業ディレクトリ変更時 | 監視 / ロギング |
InstructionsLoaded | 命令ファイルロード時 | 監視 / ロギング |
FileChanged | ファイルの作成/変更/削除時 | 監視 / ロギング |
PermissionRequest | パーミッション要求時 | パーミッション要求を自動承認 / 拒否 |
完全なイベント型定義は Hooks リファレンス を参照してください。
QueryOptions.hooks で hooks を設定します:
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback, HookCallbackMatcher } from '@qoder-ai/qoder-agent-sdk';
const result = query({
prompt: 'perform task',
options: {
hooks: {
PreToolUse: [{ matcher: 'Bash', hooks: [myHook] }],
PostToolUse: [{ hooks: [auditHook] }],
SessionEnd: [{ hooks: [logHook] }],
},
},
});
for await (const message of result) {
// process messages
}
Matcher
matcher フィールドは正規表現パターンです。ツール名がマッチした場合のみ hook が実行されます:
const result = query({
prompt: 'perform task',
options: {
hooks: {
PreToolUse: [
{ matcher: 'Bash', hooks: [bashAuditHook] }, // Bash のみ
{ matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] }, // ファイル操作
{ hooks: [generalLogHook] }, // すべてのツール(matcher なし)
],
},
},
});
コールバック関数
各 hook コールバックはイベント入力、ツール使用 ID、abort signal を受け取ります:
type HookCallback = (
input: HookInput,
toolUseID: string | undefined,
options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;
すべてのイベントは共通フィールドを共有します:hook_event_name(イベントタイプ)、session_id(セッション ID)、transcript_path(記録ファイルパス)、cwd(作業ディレクトリ)。各イベントにはさらに固有のフィールドがあります(例:PreToolUse の tool_name と tool_input)。
完全な入力型定義は Hooks リファレンス を参照してください。
コールバックは以下のフィールドで動作を制御するオブジェクトを返します:
continue: false — セッションを終了
decision: "block" + reason — ツール実行を阻止、または AI の停止を防止
hookSpecificOutput — イベント固有の出力。ツール入力の変更(updatedInput)、ツール出力の上書き(updatedToolOutput)、コンテキスト注入(additionalContext)など
完全な出力型定義は Hooks リファレンス を参照してください。
サンプル
危険なシェルコマンドをブロック:
const securityHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PreToolUse') return {};
if (input.tool_name === 'Bash') {
const cmd = String((input.tool_input as any)?.command ?? '');
if (cmd.includes('rm -rf')) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'Destructive delete operations are not allowed',
},
};
}
}
return {};
};
機密情報のマスキング(PostToolUse)
ツール出力を上書きし、AK/Token などの機密情報を置換:
const secretRedactHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PostToolUse') return {};
const content = typeof input.tool_response === 'string'
? input.tool_response
: JSON.stringify(input.tool_response);
const redacted = content
.replace(/(?:LTAI|AKID)[A-Za-z0-9]{16,}/g, '<REDACTED_AK>')
.replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g, 'Bearer <REDACTED>');
if (redacted === content) return {};
return {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
updatedToolOutput: redacted,
},
};
};
長い出力のトリミング(PostToolUse)
長すぎる Bash 出力を先頭と末尾を残して切り詰め:
const bashSummarizeHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PostToolUse') return {};
if (input.tool_name !== 'Bash') return {};
const content = String(input.tool_response ?? '');
const THRESHOLD = 50 * 1024;
if (content.length <= THRESHOLD) return {};
const head = content.slice(0, 8 * 1024);
const tail = content.slice(-4 * 1024);
const omitted = content.length - head.length - tail.length;
return {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
updatedToolOutput: `${head}\n\n[... OMITTED ${omitted} chars ...]\n\n${tail}`,
},
};
};
強制続行(Stop)
タスクが未完了の場合、AI の停止を防止:
const keepGoingHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'Stop') return {};
if (!isTaskComplete()) {
return {
decision: 'block',
reason: 'Please continue completing the remaining tasks',
};
}
return {};
};
パーミッションの自動承認(PermissionRequest)
Read ツールのパーミッション要求を自動承認:
const autoApproveRead: HookCallback = async (input) => {
if (input.hook_event_name !== 'PermissionRequest') return {};
if (input.tool_name === 'Read') {
return {
hookSpecificOutput: {
hookEventName: 'PermissionRequest',
decision: { behavior: 'allow' },
},
};
}
return {};
};
完全なパーミッションモデルはパーミッションを参照してください。
監査とセキュリティ制御(総合)
監査ログとセキュリティインターセプトの組み合わせ:
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback } from '@qoder-ai/qoder-agent-sdk';
import * as fs from 'fs';
const auditLog = fs.createWriteStream('audit.log', { flags: 'a' });
const securityHook: HookCallback = async (input, toolUseID) => {
if (input.hook_event_name === 'PreToolUse') {
// 監査ログ
auditLog.write(JSON.stringify({
event: 'tool_call',
tool: input.tool_name,
input: input.tool_input,
timestamp: new Date().toISOString(),
}) + '\n');
// セキュリティチェック:外部ドメインへの curl をブロック
if (input.tool_name === 'Bash') {
const cmd = String((input.tool_input as any)?.command ?? '');
if (/curl\s+https?:\/\/(?!localhost)/.test(cmd)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'HTTP requests to external domains are not allowed',
},
};
}
}
}
return {};
};
const result = query({
prompt: 'run deployment',
options: {
hooks: {
PreToolUse: [{ hooks: [securityHook] }],
},
},
});
for await (const message of result) {
// process messages
}
注意事項
- Hook コールバックは AI の実行をブロックしないよう、迅速に返すべきです。
matcher は JavaScript 正規表現構文を使用し、tool_name フィールドにマッチします。
continue: false はセッションを終了します——PreToolUse、PostToolUse、PostToolUseFailure、UserPromptSubmit、Stop、SubagentStop イベントでのみ有効です。観察系イベント(SessionEnd、CwdChanged など)はこのフィールドを無視します。
- 複数の hook が競合する
decision 値を返した場合、"deny" / "block" が優先されます(最も厳格なルールが適用)。
- 複数の hook が
updatedToolOutput を設定した場合、最後の非空値が有効になります。チェーン変換(例:マスキング後にトリミング)が必要な場合は、単一のコールバック内で順次実行してください。