メインコンテンツへスキップ

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セッション終了時クリーンアップ / ロギング
StopAI が生成を停止した時停止の防止、続行の強制
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(作業ディレクトリ)。各イベントにはさらに固有のフィールドがあります(例:PreToolUsetool_nametool_input)。 完全な入力型定義は Hooks リファレンス を参照してください。

出力

コールバックは以下のフィールドで動作を制御する dict を返します:
  • continue_: False — セッションを終了(JSON "continue" にシリアライズされます)
  • decision: "block" + reason — ツール実行を阻止、または AI の停止を防止
  • hookSpecificOutput — イベント固有の出力。ツール入力の変更(updatedInput)、ツール出力の上書き(updatedToolOutput)、コンテキスト注入(additionalContext)など
完全な出力型定義は Hooks リファレンス を参照してください。

サンプル

セキュリティインターセプト(PreToolUse)

危険なシェルコマンドをブロック:
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 はセッションを終了します——PreToolUsePostToolUsePostToolUseFailureUserPromptSubmitStopSubagentStop イベントでのみ有効です。観察系イベント(SessionEndCwdChanged など)はこのフィールドを無視します。
  • 複数の hook が競合する decision 値を返した場合、"deny" / "block" が優先されます(最も厳格なルールが適用)。
  • 複数の hook が updatedToolOutput を設定した場合、最後の非空値が有効になります。チェーン変換(例:マスキング後にトリミング)が必要な場合は、単一のコールバック内で順次実行してください。
  • Python SDK は Python の予約語との衝突を避けるため、末尾アンダースコア付きフィールド名(continue_)を使用します。SDK はシリアライズ時に自動的にワイヤープロトコル名(continue)に変換します。