Skip to main content
制御と安全性

Hooks

Hooks を使用すると、AI セッションの主要なライフサイクルポイントにカスタムロジックを注入でき、監査ログ、セキュリティ制御、コンテキスト注入、動的な動作変更が可能になります。

イベント概要

イベントトリガー制御可能な動作
PreToolUseツール呼び出し前インターセプト / 許可 / 入力の変更
PostToolUseツール成功後監査 / コンテキスト注入 / 出力の上書き
PostToolUseFailureツール失敗後エラーハンドリング / ロギング
UserPromptSubmitユーザープロンプト送信前コンテキスト注入 / インターセプト
SessionStartセッション開始時初期化 / コンテキスト注入
SessionEndセッション終了時クリーンアップ / ロギング
StopAI が生成を停止した時停止の防止、続行の強制
SubagentStartサブエージェント開始時監視 / ロギング
SubagentStopサブエージェント停止時監視 / ロギング
PreCompactコンテキスト圧縮前監視 / ロギング
PostCompactコンテキスト圧縮後監視 / ロギング
CwdChanged作業ディレクトリ変更時監視 / ロギング
InstructionsLoaded命令ファイルロード時監視 / ロギング
FileChangedファイルの作成/変更/削除時監視 / ロギング
PermissionRequestパーミッション要求時パーミッション要求を自動承認 / 拒否
PermissionDeniedパーミッション要求が拒否された後拒否結果の監視 / 監査
WorktreeCreate(TypeScript SDK)管理対象 Worktree の作成要求時組み込みの物理作成を置き換える
WorktreeRemove(TypeScript SDK)Hook が作成した Worktree の削除時組み込みの物理削除を置き換える
完全なイベント型定義は SDK References を参照してください。

Worktree リソース置換 Hook(TypeScript)

WorktreeCreateWorktreeRemove は、読み取り専用の通知ではなくリソース置換 Hook です。WorktreeCreate を登録した場合、コールバックは Worktree を実際に作成してパスを返す必要があります。対応する WorktreeRemove コールバックは、Hook が作成したリソースを実際に削除する必要があります。セッションの cwd 切り替え、transcript の移動、Resume 状態、Exit ライフサイクルは引き続き Qoder CLI が管理します。
const hooks = {
  WorktreeCreate: [{
    hooks: [async (input) => {
      const worktreePath = await createWorktree(input.name);
      return {
        hookSpecificOutput: {
          hookEventName: 'WorktreeCreate',
          worktreePath,
        },
      };
    }],
  }],
  WorktreeRemove: [{
    hooks: [async (input) => {
      await removeWorktree(input.worktree_path);
      return {};
    }],
  }],
};
ここでの createWorktreeremoveWorktree は、Git またはホスト固有の処理を行うアプリケーション側のヘルパーであり、SDK の組み込みメソッドではありません。

設定

options の 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 only
        { matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] },  // File operations
        { hooks: [generalLogHook] },                            // All tools (no matcher)
      ],
    },
  },
});

コールバック関数

各 hook コールバックはイベント入力、ツール呼び出し ID、コンテキスト(TypeScript では abort signal)を受け取ります:
type HookCallback = (
  input: HookInput,
  toolUseID: string | undefined,
  options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;

入力

すべてのイベントは共通フィールドを共有します:hook_event_name(イベントタイプ)、session_id(セッション ID)、transcript_path(記録ファイルパス)、cwd(作業ディレクトリ)。各イベントにはさらに固有のフィールドがあります(例:PreToolUsetool_nametool_input)。 完全な入力型定義は SDK References を参照してください。

出力

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

使用例

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

危険なシェルコマンドをブロック:
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') {
    // Audit log
    auditLog.write(JSON.stringify({
      event: 'tool_call',
      tool: input.tool_name,
      input: input.tool_input,
      timestamp: new Date().toISOString(),
    }) + '\n');

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