メインコンテンツへスキップ
Hooksは、Qoder CLIの重要なポイントでAgentのメイン実行フローに介入しながら、CLIとの疎結合を保つ仕組みです。主な用途として、ツール実行前の危険な操作の遮断、タスク完了後のデスクトップ通知送信、ファイル書き込み後の自動lintなどが挙げられます。 HooksはJSON設定ファイルで定義されるため、コードを変更する必要はありません。設定ファイルを編集するだけで即座に有効になります。

クイックスタート

以下の例では、HookでAgentがrm -rfを実行しようとした際に自動的に阻止する危険なコマンド遮断の実装を示します。 ステップ1:スクリプトを作成する
mkdir -p ~/.qoder/hooks
cat > ~/.qoder/hooks/block-rm.sh << 'EOF'
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')

if echo "$command" | grep -q 'rm -rf'; then
  echo "危険なコマンドをブロックしました: $command" >&2
  exit 2
fi

exit 0
EOF
chmod +x ~/.qoder/hooks/block-rm.sh
ステップ2:設定ファイルを編集する ~/.qoder/settings.jsonに以下を追加します:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
ステップ3:動作を確認する Qoder CLIを起動し、Agentにrm -rfを含むコマンドを実行させてみてください。Hookが実行を阻止し、Agentに通知します。

設定

設定ファイルの場所

Hook設定は以下の3つのファイルから読み込まれ、3段階の設定がマージされて実行されます:
~/.qoder/settings.json                    # ユーザーレベル。すべてのプロジェクトに適用
${project}/.qoder/settings.json           # プロジェクトレベル。現在のプロジェクトに適用。gitにコミットしてチームと共有可能
${project}/.qoder/settings.local.json     # プロジェクトローカルレベル。最高優先度。.gitignoreへの追加を推奨

設定フォーマット

{
  "hooks": {
    "イベント名": [
      {
        "matcher": "マッチ条件",
        "hooks": [
          {
            "type": "command",
            "command": "実行するコマンド",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
フィールドの説明:
フィールド必須説明
typeはい固定値:"command"
commandはい実行するシェルコマンド
timeoutいいえタイムアウト秒数(デフォルト:60)
matcherいいえマッチ条件。省略するとすべてにマッチ
1つのイベントに複数のmatcherグループを設定でき、各グループに複数のhookコマンドを含めることができます。

matcherのルール

matcherはhookのトリガー範囲をフィルタリングします。イベントごとにマッチする対象が異なります(各イベントの説明を参照)。
書き方意味
省略または"*"すべてにマッチすべてのツールでトリガー
固定値完全一致"Bash" はBashツールのみマッチ
|区切り複数値にマッチ"Write|Edit" はWriteまたはEditにマッチ
正規表現正規表現マッチ"mcp__.*" はすべてのMCPツールにマッチ

Hookスクリプトの書き方

Hookスクリプトはstdinを通してJSON入力を受け取り、exit codeとstdoutによって動作を制御します。本セクションではすべてのイベントに共通する入出力フォーマットを説明します。各イベント固有の追加フィールドはサポートされるイベントを参照してください。

入力

HookスクリプトはstdinからJSONデータを受け取ります。すべてのイベントに以下の共通フィールドが含まれます:
フィールド説明
session_id現在のセッションID
cwd現在の作業ディレクトリ
hook_event_nameトリガーされたイベント名
イベントごとにこれらに加えて追加フィールドが付加されます(各イベントの説明を参照)。 jqを使った入力の解析例:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')

出力

Hookはexit codeとstdoutで動作を制御します。 exit 0 は成功を意味します。Qoder CLIはstdoutを解析します。一部のイベント(UserPromptSubmit、SessionStartなど)ではプレーンテキストによるコンテキスト注入またはJSONによる細かい制御をサポートしています。詳細は各イベントの説明を参照してください。 exit 2 はブロッキングエラーを意味し、ブロッキングをサポートするイベントにのみ有効です。stdoutは無視され、stderrがエラーメッセージとしてAgentにフィードバックされます。具体的な効果はイベントによって異なります:PreToolUseはツール実行を阻止し、UserPromptSubmitはpromptを拒否し、StopはAgentの停止を阻止するなどです。 その他のexit code は非ブロッキングエラーとして扱われ、実行フローに影響を与えません。stderrはログにのみ記録されます。

環境変数

Hookスクリプト実行時に以下の環境変数が利用できます:
変数説明
QODER_PROJECT_DIR現在のプロジェクトの作業ディレクトリ

サポートされるイベント

Qoder CLIは以下のHookイベントをサポートし、セッションライフサイクルの各段階をカバーしています。

SessionStart

セッション開始時にトリガーされます。 matcherの対象: セッションのソース
matcher値トリガーシナリオ
startup新しいセッション開始時
resume既存セッションの再開時
compactコンテキスト圧縮完了後
追加入力フィールド:
{
  "source": "startup",
  "model": "Auto"
}

SessionEnd

セッション終了時にトリガーされます。 matcherの対象: 終了理由
matcher値トリガーシナリオ
prompt_input_exitユーザーが入力を終了(Ctrl+Dなど)
otherその他の理由
追加入力フィールド:
{
  "reason": "prompt_input_exit"
}

UserPromptSubmit

ユーザーがpromptを送信した後、Agentが処理する前にトリガーされます。Agentに追加コンテキストを注入したり、特定のpromptを検証・阻止したりできます。matcherはサポートされず、設定されたすべてのhookが実行されます。 共通フィールドに加えて、UserPromptSubmitはユーザーが送信したテキストを含むpromptフィールドも受け取ります:
{
  "prompt": "ソート関数を書いてください"
}
出力制御: exit 0のとき、Agentにコンテキストを追加する方法が2つあります:stdoutに直接プレーンテキストを出力する方法と、JSONのadditionalContextフィールドで注入する方法です。プレーンテキストはより簡単ですが、{で始まるとJSONとして解析されるため注意が必要です。 promtを阻止するには、exit 2(stderrの内容がユーザーに表示される)を使うか、exit 0でdecision: "block"を含むJSONを出力します。exit 2は純粋な遮断シナリオに適しています。JSON方式はより柔軟で、同じスクリプトで条件に応じて阻止か通過かを決定できます。
フィールド説明
decision"block"に設定するとpromptの処理を阻止しコンテキストから除去。未設定の場合は通過
reason阻止時にユーザーに表示される理由。コンテキストには追加されない
additionalContextAgentに注入するコンテキスト文字列(hookSpecificOutput経由で渡される)
{
  "decision": "block",
  "reason": "ブロックの理由説明",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "注入するコンテキスト内容"
  }
}
JSON形式は必須ではありません。シンプルなケースではexit 0でプレーンテキストを出力するだけでコンテキストを注入できます。promptを阻止したり細かい制御が必要な場合のみJSONが必要です。
完全な例はpromptの送信時にコンテキストを注入する機密情報を含むpromptをブロックするを参照してください。

PreToolUse

ツール実行前にトリガーされます。ツールの実行を阻止できます。 matcherの対象: ツール名(例:BashWriteEditReadGlobGrep、MCPツール名はmcp__server__tool形式) 追加入力フィールド:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf /tmp/build"},
  "tool_use_id": "toolu_01ABC123"
}
ツール実行の阻止: exit code 2を返します。stderrの内容がエラーとしてAgentに返されます。完全な例はクイックスタートを参照してください。

PostToolUse

ツールが正常に実行された後にトリガーされます。 matcherの対象: ツール名 追加入力フィールド:
{
  "tool_name": "Write",
  "tool_input": {"file_path": "/path/to/file.ts", "content": "..."},
  "tool_response": "File written successfully",
  "tool_use_id": "toolu_01ABC123"
}

PostToolUseFailure

ツールの実行が失敗した後にトリガーされます。 matcherの対象: ツール名 追加入力フィールド:
{
  "tool_name": "Bash",
  "tool_input": {"command": "npm test"},
  "tool_use_id": "toolu_01ABC123",
  "error": "Command exited with non-zero status code 1",
  "is_interrupt": false
}

Stop

Agentがレスポンスを完了した後(メインAgent、保留中のツール呼び出しがない状態)にトリガーされます。Agentの停止を阻止して、作業を継続させることができます。 Agentの停止を阻止する: exit code 2を返します。stderrの内容がメッセージとして会話に注入され、Agentが作業を続けます。

SubagentStart / SubagentStop

サブAgentの起動時と完了時にトリガーされます。SubagentStopはStopと同様で、サブAgentの停止を阻止できます。 matcherの対象: Agentタイプ名 追加入力フィールド:
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task"
}

PreCompact

コンテキスト圧縮前にトリガーされます。 matcherの対象: トリガー方法
matcher値トリガーシナリオ
manualユーザーが手動で/compactを実行
autoコンテキストウィンドウが満杯になった際に自動トリガー
追加入力フィールド:
{
  "trigger": "manual",
  "custom_instructions": "すべてのツール呼び出し結果を保持する"
}

Notification

通知イベントがトリガーされます(権限リクエスト、タスク完了など)。 matcherの対象: 通知タイプ
matcher値トリガーシナリオ
permission権限リクエスト通知
resultAgentの結果通知
追加入力フィールド:
{
  "message": "Agent is requesting permission to run: rm -rf node_modules",
  "title": "Permission Required",
  "notification_type": "permission"
}

PermissionRequest

ツールの実行にユーザーの認可が必要な際にトリガーされます。 matcherの対象: ツール名 追加入力フィールド:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf node_modules"}
}

実用的な使用例

デスクトップ通知

Agentがタスクを完了したり認可が必要な際にデスクトップ通知をポップアップ表示します。 スクリプト ~/.qoder/hooks/notify.sh(macOS):
#!/bin/bash
input=$(cat)
message=$(echo "$input" | jq -r '.message')

if echo "$message" | grep -q "^Agent"; then
  osascript -e 'display notification "タスクが完了しました" with title "Qoder CLI"'
else
  osascript -e 'display notification "タスクに認可が必要です" with title "Qoder CLI"'
fi

exit 0
設定:
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

ファイル書き込み後の自動Lint

Agentがファイルを書き込みまたは編集するたびに、自動的にlintチェックを実行します。 スクリプト ${project}/.qoder/hooks/auto-lint.sh
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

# JS/TSファイルのみチェック
case "$file_path" in
  *.js|*.ts|*.jsx|*.tsx)
    npx eslint "$file_path" --fix 2>/dev/null
    ;;
esac

exit 0
設定:イベント PostToolUse、matcher Write|Edit、command .qoder/hooks/auto-lint.sh

Agentの作業を継続させる

Agentが停止した際に未完了のタスクがないか確認し、あればメッセージを注入してAgentに作業を続けさせます。 スクリプト ~/.qoder/hooks/check-continue.sh
#!/bin/bash
# コミットされていないgitの変更を確認
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
  echo "コミットされていない変更が検出されました。git commitを完了してください" >&2
  exit 2
fi

exit 0
設定:イベント Stop、command ~/.qoder/hooks/check-continue.sh

promptの送信時にコンテキストを注入する

ユーザーの質問前に、現在のgitブランチ情報をAgentのコンテキストとして自動注入します。 スクリプト ~/.qoder/hooks/inject-branch.sh
#!/bin/bash
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -n "$branch" ]; then
  echo "現在のgitブランチ: $branch"
fi
exit 0
設定:イベント UserPromptSubmit、command ~/.qoder/hooks/inject-branch.sh

機密情報を含むpromptをブロックする

パスワードやキーなどの機密キーワードを含むpromptを遮断し、Agentへの送信を防ぎます。 スクリプト ~/.qoder/hooks/block-sensitive.sh
#!/bin/bash
input=$(cat)
prompt=$(echo "$input" | jq -r '.prompt')

if echo "$prompt" | grep -qi 'password\|secret\|credential'; then
  echo "promptに機密情報が含まれているためブロックしました" >&2
  exit 2
fi

exit 0
設定:イベント UserPromptSubmit、command ~/.qoder/hooks/block-sensitive.sh