> ## 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

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

<div id="イベント概要" />

## イベント概要

| イベント                 | トリガー           | 制御可能な動作                |
| -------------------- | -------------- | ---------------------- |
| `PreToolUse`         | ツール呼び出し前       | インターセプト / 許可 / 入力の変更   |
| `PostToolUse`        | ツール成功後         | 監査 / コンテキスト注入 / 出力の上書き |
| `PostToolUseFailure` | ツール失敗後         | エラーハンドリング / ロギング       |
| `UserPromptSubmit`   | ユーザープロンプト送信前   | コンテキスト注入 / インターセプト     |
| `SessionStart`       | セッション開始時       | 初期化 / コンテキスト注入         |
| `SessionEnd`         | セッション終了時       | クリーンアップ / ロギング         |
| `Stop`               | AI が生成を停止した時   | 停止の防止、続行の強制            |
| `SubagentStart`      | サブエージェント開始時    | 監視 / ロギング              |
| `SubagentStop`       | サブエージェント停止時    | 監視 / ロギング              |
| `PreCompact`         | コンテキスト圧縮前      | 監視 / ロギング              |
| `PostCompact`        | コンテキスト圧縮後      | 監視 / ロギング              |
| `CwdChanged`         | 作業ディレクトリ変更時    | 監視 / ロギング              |
| `InstructionsLoaded` | 命令ファイルロード時     | 監視 / ロギング              |
| `FileChanged`        | ファイルの作成/変更/削除時 | 監視 / ロギング              |
| `PermissionRequest`  | パーミッション要求時     | パーミッション要求を自動承認 / 拒否    |

完全なイベント型定義は [Hooks リファレンス](/ja/cli/sdk/references#hooks-リファレンス) を参照してください。

<div id="設定" />

## 設定

`QueryOptions.hooks` で hooks を設定します：

```typescript theme={null}
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
}
```

<div id="matcher" />

### Matcher

`matcher` フィールドは正規表現パターンです。ツール名がマッチした場合のみ hook が実行されます：

```typescript theme={null}
const result = query({
  prompt: 'perform task',
  options: {
    hooks: {
      PreToolUse: [
        { matcher: 'Bash', hooks: [bashAuditHook] },           // Bash のみ
        { matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] },  // ファイル操作
        { hooks: [generalLogHook] },                            // すべてのツール（matcher なし）
      ],
    },
  },
});
```

<div id="コールバック関数" />

### コールバック関数

各 hook コールバックはイベント入力、ツール使用 ID、abort signal を受け取ります：

```typescript theme={null}
type HookCallback = (
  input: HookInput,
  toolUseID: string | undefined,
  options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;
```

<div id="入力" />

#### 入力

すべてのイベントは共通フィールドを共有します：`hook_event_name`（イベントタイプ）、`session_id`（セッション ID）、`transcript_path`（記録ファイルパス）、`cwd`（作業ディレクトリ）。各イベントにはさらに固有のフィールドがあります（例：`PreToolUse` の `tool_name` と `tool_input`）。

完全な入力型定義は [Hooks リファレンス](/ja/cli/sdk/references#basehookinput) を参照してください。

<div id="出力" />

#### 出力

コールバックは以下のフィールドで動作を制御するオブジェクトを返します：

* `continue: false` — セッションを終了
* `decision: "block"` + `reason` — ツール実行を阻止、または AI の停止を防止
* `hookSpecificOutput` — イベント固有の出力。ツール入力の変更（`updatedInput`）、ツール出力の上書き（`updatedToolOutput`）、コンテキスト注入（`additionalContext`）など

完全な出力型定義は [Hooks リファレンス](/ja/cli/sdk/references#hookjsonoutput) を参照してください。

<div id="サンプル" />

## サンプル

<div id="セキュリティインターセプトpretooluse" />

### セキュリティインターセプト（PreToolUse）

危険なシェルコマンドをブロック：

```typescript theme={null}
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 {};
};
```

<div id="機密情報のマスキングposttooluse" />

### 機密情報のマスキング（PostToolUse）

ツール出力を上書きし、AK/Token などの機密情報を置換：

```typescript theme={null}
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,
    },
  };
};
```

<div id="長い出力のトリミングposttooluse" />

### 長い出力のトリミング（PostToolUse）

長すぎる Bash 出力を先頭と末尾を残して切り詰め：

```typescript theme={null}
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}`,
    },
  };
};
```

<div id="強制続行stop" />

### 強制続行（Stop）

タスクが未完了の場合、AI の停止を防止：

```typescript theme={null}
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 {};
};
```

<div id="パーミッションの自動承認permissionrequest" />

### パーミッションの自動承認（PermissionRequest）

Read ツールのパーミッション要求を自動承認：

```typescript theme={null}
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 {};
};
```

> 完全なパーミッションモデルは[パーミッション](/ja/cli/sdk/permissions)を参照してください。

<div id="監査とセキュリティ制御総合" />

### 監査とセキュリティ制御（総合）

監査ログとセキュリティインターセプトの組み合わせ：

```typescript theme={null}
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
}
```

***

<div id="注意事項" />

## 注意事項

* Hook コールバックは AI の実行をブロックしないよう、迅速に返すべきです。
* `matcher` は JavaScript 正規表現構文を使用し、`tool_name` フィールドにマッチします。
* `continue: false` はセッションを終了します——`PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`UserPromptSubmit`、`Stop`、`SubagentStop` イベントでのみ有効です。観察系イベント（`SessionEnd`、`CwdChanged` など）はこのフィールドを無視します。
* 複数の hook が競合する `decision` 値を返した場合、`"deny"` / `"block"` が優先されます（最も厳格なルールが適用）。
* 複数の hook が `updatedToolOutput` を設定した場合、**最後の非空値**が有効になります。チェーン変換（例：マスキング後にトリミング）が必要な場合は、単一のコールバック内で順次実行してください。
