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 リファレンス を参照してください。
QoderAgentOptions.hooks で hooks を設定します:
from qoder_agent_sdk import query, QoderAgentOptions, HookMatcher
async for msg in query(
prompt="perform task",
options=QoderAgentOptions(
hooks={
"PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_hook])],
"PostToolUse": [HookMatcher(hooks=[audit_hook])],
"SessionEnd": [HookMatcher(hooks=[log_hook])],
},
),
):
... # process messages
Matcher
matcher フィールドは正規表現パターンです。ツール名がマッチした場合のみ hook が実行されます:
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[bash_audit]), # Bash のみ
HookMatcher(matcher="File.*|Write|Edit", hooks=[file_audit]), # ファイル操作
HookMatcher(hooks=[general_log]), # すべてのツール(matcher なし)
],
}
コールバック関数
各 hook コールバックはイベント入力、ツール使用 ID、コンテキストを受け取ります:
HookCallback = Callable[
[HookInput, str | None, HookContext],
Awaitable[HookJSONOutput],
]
すべてのイベントは共通フィールドを共有します:hook_event_name(イベントタイプ)、session_id(セッション ID)、transcript_path(記録ファイルパス)、cwd(作業ディレクトリ)。各イベントにはさらに固有のフィールドがあります(例:PreToolUse の tool_name と tool_input)。
完全な入力型定義は Hooks リファレンス を参照してください。
コールバックは以下のフィールドで動作を制御する dict を返します:
continue_: False — セッションを終了(JSON "continue" にシリアライズされます)
decision: "block" + reason — ツール実行を阻止、または AI の停止を防止
hookSpecificOutput — イベント固有の出力。ツール入力の変更(updatedInput)、ツール出力の上書き(updatedToolOutput)、コンテキスト注入(additionalContext)など
完全な出力型定義は Hooks リファレンス を参照してください。
サンプル
危険なシェルコマンドをブロック:
async def security_hook(inp: HookInput, tid: str | None, ctx: HookContext) -> HookJSONOutput:
if inp.get("tool_name") == "Bash":
cmd = (inp.get("tool_input") or {}).get("command", "")
if "rm -rf" in cmd:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive delete operations are not allowed",
},
}
return {}
機密情報のマスキング(PostToolUse)
ツール出力を上書きし、AK/Token などの機密情報を置換:
import re
async def secret_redact_hook(inp: HookInput, tid: str | None, ctx: HookContext) -> HookJSONOutput:
if inp.get("hook_event_name") != "PostToolUse":
return {}
response = inp.get("tool_response", "")
content = response if isinstance(response, str) else json.dumps(response)
redacted = re.sub(r"(?:LTAI|AKID)[A-Za-z0-9]{16,}", "<REDACTED_AK>", content)
redacted = re.sub(r"Bearer\s+[A-Za-z0-9\-._~+/]+=*", "Bearer <REDACTED>", redacted)
if redacted == content:
return {}
return {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"updatedToolOutput": redacted,
},
}
長い出力のトリミング(PostToolUse)
長すぎる Bash 出力を先頭と末尾を残して切り詰め:
async def bash_summarize_hook(inp: HookInput, tid: str | None, ctx: HookContext) -> HookJSONOutput:
if inp.get("hook_event_name") != "PostToolUse":
return {}
if inp.get("tool_name") != "Bash":
return {}
content = str(inp.get("tool_response") or "")
THRESHOLD = 50 * 1024
if len(content) <= THRESHOLD:
return {}
head = content[:8 * 1024]
tail = content[-4 * 1024:]
omitted = len(content) - len(head) - len(tail)
return {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"updatedToolOutput": f"{head}\n\n[... OMITTED {omitted} chars ...]\n\n{tail}",
},
}
強制続行(Stop)
タスクが未完了の場合、AI の停止を防止:
async def keep_going(inp: HookInput, tid: str | None, ctx: HookContext) -> HookJSONOutput:
if inp.get("hook_event_name") != "Stop":
return {}
if not is_task_complete():
return {"decision": "block", "reason": "Please continue completing the remaining tasks"}
return {}
パーミッションの自動承認(PermissionRequest)
Read ツールのパーミッション要求を自動承認:
async def auto_approve_read(inp: HookInput, tid: str | None, ctx: HookContext) -> HookJSONOutput:
if inp.get("hook_event_name") != "PermissionRequest":
return {}
if inp.get("tool_name") == "Read":
return {
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {"behavior": "allow"},
},
}
return {}
完全なパーミッションモデルはパーミッションドキュメントを参照してください。
監査とセキュリティ制御(総合)
監査ログとセキュリティインターセプトの組み合わせ:
import json
import logging
import re
from qoder_agent_sdk import query, QoderAgentOptions, HookMatcher
from qoder_agent_sdk.types import HookInput, HookContext, HookJSONOutput
async def security_hook(inp: HookInput, tid: str | None, ctx: HookContext) -> HookJSONOutput:
if inp.get("hook_event_name") != "PreToolUse":
return {}
# Audit log
logging.info(json.dumps({
"event": "tool_call",
"tool": inp.get("tool_name"),
"input": inp.get("tool_input"),
}))
# Security check: block curl to external domains
if inp.get("tool_name") == "Bash":
cmd = str((inp.get("tool_input") or {}).get("command", ""))
if re.search(r"curl\s+https?://(?!localhost)", cmd):
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "HTTP requests to external domains are not allowed",
},
}
return {}
async def main():
async for msg in query(
prompt="run deployment",
options=QoderAgentOptions(
hooks={
"PreToolUse": [HookMatcher(hooks=[security_hook])],
},
),
):
pass # process messages
注意事項
- Hook コールバックは AI の実行をブロックしないよう、迅速に返すべきです。
matcher は Python の正規表現構文(re モジュール)を使用し、tool_name フィールドにマッチします。
continue_: False はセッションを終了します——PreToolUse、PostToolUse、PostToolUseFailure、UserPromptSubmit、Stop、SubagentStop イベントでのみ有効です。観察系イベント(SessionEnd、CwdChanged など)はこのフィールドを無視します。
- 複数の hook が競合する
decision 値を返した場合、"deny" / "block" が優先されます(最も厳格なルールが適用)。
- 複数の hook が
updatedToolOutput を設定した場合、最後の非空値が有効になります。チェーン変換(例:マスキング後にトリミング)が必要な場合は、単一のコールバック内で順次実行してください。
- Python SDK は Python の予約語との衝突を避けるため、末尾アンダースコア付きフィールド名(
continue_)を使用します。SDK はシリアライズ時に自動的にワイヤープロトコル名(continue)に変換します。