Hooks を使うと、QoderWork の実行フローにおける重要なポイントで、コードを変更することなくカスタムロジックを挿入できます。JSON 設定ファイルを編集するだけで、たとえば次のようなことを実現できます。
- ツール実行前に危険な操作をブロック
- ファイル書き込みのたびに自動で lint を実行し、コードスタイルを統一
- Agent のタスク完了時にデスクトップ通知を送り、画面に張り付く必要をなくす
Prompt による指示とは異なり、Hooks は確定的に動作します。イベントが発火すればスクリプトは必ず実行され、モデルの解釈に結果が左右されることはありません。
クイックスタート
以下の例では、rm -rf のような危険なコマンドをブロックする方法を紹介します。
スクリプトを作成
mkdir -p ~/.qoderwork/hooks
cat > ~/.qoderwork/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 ~/.qoderwork/hooks/block-rm.sh
設定を追加
~/.qoderwork/settings.json に以下を追加します:{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/block-rm.sh"
}
]
}
]
}
}
動作を確認
QoderWork を開き、Agent に rm -rf を含むコマンドを実行させてみてください。Hook が実行をブロックし、エラーメッセージを Agent にフィードバックします。
仕組み
Hook のライフサイクルは 3 ステップにまとめられます:スクリプトを書く → 設定に登録する → 自動的に有効になる。
Agent がライフサイクルイベント(例:ツール呼び出し前)に到達すると、QoderWork はそのイベントに Hook が登録されているか確認します:
- QoderWork は起動時にすべての Hook 設定を読み込む
- Agent 実行中にライフサイクルイベント(例:
PreToolUse)に到達
- QoderWork はそのイベントに登録されたすべての Hook グループを走査し、
matcher で現在のコンテキストと照合
- マッチした Hook は順番にシェルスクリプトを実行
- スクリプトは
stdin 経由で JSON イベントコンテキストを受け取り、exit code と stdout で判断結果を返す
- QoderWork は結果を読み取り、次のアクション(続行またはブロック)を決定
前提条件
- jq:サンプルスクリプトは jq を使って JSON を解析します。macOS では
brew install jq、Linux では apt install jq でインストールしてください。
- スクリプト権限:すべての Hook スクリプトに実行権限が必要です(
chmod +x)。
Hooks の作成
1. 要件の決定:イベントと Matcher を選ぶ
まず、どこに介入し、何をマッチさせるかを決めます:
[どのタイミングで] [どの操作を] インターセプト/処理したいか?
↓ ↓
イベントを選ぶ matcher を書く
| 要件 | イベント | matcher |
|---|
| Agent がシェルコマンドを実行する前にチェック | PreToolUse | "Bash" |
| Agent がファイルを書き込み・編集した後に処理 | PostToolUse | "Write | Edit" |
| ツール呼び出し失敗時にログを記録 | PostToolUseFailure | "Bash" または省略 |
| すべてのユーザー Prompt をスクリーニング | UserPromptSubmit | 省略(すべてにマッチ) |
| Agent 停止時に通知をトリガー | Stop | 省略(すべてにマッチ) |
| MCP ツールのみをインターセプト | PreToolUse | "mcp__.*" |
2. Hook スクリプトを書く
Hook スクリプトは以下のプロトコルに従う標準的なシェルスクリプトです:
入力:stdin 経由で JSON イベントコンテキストが渡されます。
出力:exit code で制御します。
exit 0 → 許可(実行を続行)
exit 2 → ブロック(操作を停止、stderr が会話に注入される)
その他 → エラー(実行を続行、stderr がユーザーに表示される)
スクリプトテンプレート:
#!/bin/bash
# 1. stdin から JSON 入力を読み取る
input=$(cat)
# 2. jq で必要なフィールドを抽出
tool_name=$(echo "$input" | jq -r '.tool_name')
tool_input=$(echo "$input" | jq -r '.tool_input')
# 3. ロジックを記述
if [ "$tool_name" = "Bash" ]; then
command=$(echo "$input" | jq -r '.tool_input.command')
if echo "$command" | grep -qE 'rm\s+-rf|DROP\s+TABLE'; then
echo "操作が拒否されました: $command" >&2
exit 2
fi
fi
# 4. 許可
exit 0
exit 0 時に JSON を stdout に出力して、より細かい制御も可能です:#!/bin/bash
input=$(cat)
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"この操作は許可されていません"}}'
exit 0
3. 設定ファイルに登録
~/.qoderwork/settings.json の対応するイベントにスクリプトパスを追加します:
{
"hooks": {
"イベント名": [
{
"matcher": "マッチ条件(任意)",
"hooks": [
{
"type": "command",
"command": "スクリプトパス"
}
]
}
]
}
}
4. テストとデバッグ
ターミナルからパイプで直接テストできます:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"},"hook_event_name":"PreToolUse"}' \
| ~/.qoderwork/hooks/block-rm.sh
echo "Exit code: $?"
stderr 出力(ブロックメッセージ)を確認:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| ~/.qoderwork/hooks/block-rm.sh 2>&1
Hooks の設定
設定ファイルの場所
QoderWork はユーザーレベルの設定ファイルから Hook 設定を読み込みます:
| 場所 | スコープ | 説明 |
|---|
~/.qoderwork/settings.json | ユーザー(グローバル) | 個人設定、すべての QoderWork セッションに適用 |
ホットリロードはまだサポートされていません — Hook 設定を変更した後は QoderWork を再起動してください。
設定フォーマット
{
"hooks": {
"イベント名": [
{
"matcher": "マッチ条件",
"hooks": [
{
"type": "command",
"command": "実行するコマンド"
}
]
}
]
}
}
| フィールド | 必須 | 説明 |
|---|
type | はい | "command" 固定 |
command | はい | 実行するシェルコマンドまたはスクリプトパス |
timeout | いいえ | タイムアウト(秒)、デフォルト 30。カスタム値は将来のリリースでサポート予定 |
matcher | いいえ | マッチ条件。省略するとそのイベントのすべての発火にマッチ |
1 つのイベントに複数の matcher グループを定義でき、各グループに複数の Hook コマンドを含めることができます。
matcher ルール
matcher は Hook の発火範囲を決定します。何にマッチするかはイベントによって異なります(各イベントの説明を参照)。
| パターン | 意味 | 例 |
|---|
省略または "*" | すべてにマッチ | すべてのツールで Hook が発火 |
| 正確な値 | 完全一致 | "Bash" は Bash ツールのみで発火 |
| 区切り | 複数の値にマッチ | "Write | Edit" は Write または Edit で発火 |
| 正規表現 | 正規表現マッチ | "mcp__.*" はすべての MCP ツールにマッチ |
ツール名マッピング
QoderWork はネイティブ名と互換名の 2 セットのツール名をサポートしています。matcher にはどちらでも使用でき、QoderWork が内部でマッピングします。
| ネイティブ名 | 互換名 | 説明 |
|---|
run_in_terminal | Bash | シェルコマンドの実行 |
read_file | Read | ファイル内容の読み取り |
create_file | Write | ファイルの作成・書き込み |
search_replace | Edit | ファイルの編集 |
delete_file | - | ファイルの削除 |
grep_code | Grep | ファイル内容の検索 |
search_file | Glob | ファイル名マッチ |
list_dir | LS | ディレクトリの一覧 |
task | Task | サブタスク・サブエージェントの起動 |
Skill | - | スキルの呼び出し |
search_web | WebSearch | Web 検索 |
fetch_content | WebFetch | Web ページコンテンツの取得 |
todo_write | TodoWrite | TODO の書き込み |
ask_user_question | - | ユーザーへの質問 |
search_memory | - | メモリの検索 |
update_memory | - | メモリの更新 |
mcp__<server>__<tool> | 同左 | MCP ツール |
Hook スクリプトの作成
Hook スクリプトは stdin 経由で JSON 入力を受け取り、exit code と stdout で動作を制御します。このセクションではすべてのイベントに共通の入出力フォーマットを説明します。イベント固有のフィールドについては Hook イベントを参照してください。
QoderWork は現在、Hook スクリプトに環境変数を注入しません。すべてのデータは stdin JSON 経由で渡されます。セッション ID、作業ディレクトリ、ツール情報が必要な場合は、stdin JSON 入力から解析してください。
Hook スクリプトは stdin 経由で JSON データを受け取ります。すべてのイベントに以下の共通フィールドが含まれます:
| フィールド | 説明 |
|---|
session_id | 現在のセッション ID |
transcript_path | セッション Transcript JSONL ファイルのパス(例:~/.qoderwork/projects/<エンコードパス>/<session-id>.jsonl) |
cwd | 現在の作業ディレクトリ |
hook_event_name | この Hook をトリガーしたイベント名 |
各イベントはこれらに加えて固有のフィールドを追加します(各 Hook イベントの説明を参照)。
jq で入力を解析:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
Hook は exit code と stdout で動作を制御します。
exit 0 は成功を示します。QoderWork は stdout を解析します。一部のイベント(UserPromptSubmit、SessionStart など)はプレーンテキストによるコンテキスト注入または JSON による細かい制御をサポートしています。詳細は各イベントの説明を参照してください。
exit 2 はブロックエラーを示し、ブロック可能なイベントでのみ有効です。stdout は無視され、stderr がエラーメッセージとして Agent にフィードバックされます。具体的な効果はイベントによって異なります:PreToolUse はツール実行をブロック、UserPromptSubmit は Prompt を拒否、Stop は Agent の停止をブロック、など。
その他の exit code は非ブロックエラーとして扱われ、実行フローに影響しません。stderr はログにのみ記録されます。
Hook イベント
UserPromptSubmit
ユーザーが Prompt を送信した後、Agent が処理を開始する前に発火します。Prompt スクリーニング、コンテンツフィルタリング、コンテキストの自動注入に使用できます。
matcher: なし。このイベントはすべてのユーザー入力に対して発火します。
追加入力フィールド:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "UserPromptSubmit",
"prompt": "ダウンロードフォルダをファイルタイプ別に整理して"
}
Prompt のブロック: exit code 2 で、stderr の内容がエラーとしてユーザーに表示され、Agent はその Prompt を処理しません。
stdout JSON 出力フィールド(exit 0 時):
| フィールド | 型 | 説明 |
|---|
hookSpecificOutput.hookEventName | string | "UserPromptSubmit" 固定 |
hookSpecificOutput.additionalContext | string | Agent の会話に注入される追加コンテキスト |
ツール実行前に発火します。ツール実行をブロックできます。 危険なコマンドのブロック、ファイルパスの検証、権限チェックに最適です。
matcher: ツール名(例:Bash、Write、Edit、Read、または mcp__server__tool のような MCP ツール名)
追加入力フィールド:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /tmp/build" }
}
ツール実行のブロック: exit code 2 で、stderr の内容がエラーとして Agent に返されます。
stdout JSON 出力フィールド(exit 0 時):
| フィールド | 型 | 説明 |
|---|
hookSpecificOutput.hookEventName | string | "PreToolUse" 固定 |
hookSpecificOutput.permissionDecision | string | "allow"、"deny"、または "ask" |
hookSpecificOutput.permissionDecisionReason | string | 判断理由 |
hookSpecificOutput.updatedInput | object | 変更後のツール入力パラメータ(任意) |
hookSpecificOutput.additionalContext | string | 追加コンテキスト(任意) |
PostToolUse
ツール実行の成功後に発火します。ブロック不可。自動 lint、ログ記録、結果分析に使用します。
matcher: ツール名
追加入力フィールド:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": { "file_path": "/path/to/file.ts", "content": "..." },
"tool_response": "File written successfully"
}
stdout JSON 出力フィールド(exit 0 時):
| フィールド | 型 | 説明 |
|---|
hookSpecificOutput.hookEventName | string | "PostToolUse" 固定 |
hookSpecificOutput.feedback | string | ユーザーに表示されるフィードバック |
PostToolUseFailure
ツール呼び出しが失敗した後に発火します。ブロック不可。エラー監視、リトライ提案、ログ記録に使用します。
matcher: ツール名
追加入力フィールド:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": { "command": "npm test" },
"error": "Command exited with non-zero status code 1"
}
| フィールド | 型 | 説明 |
|---|
error | string | 失敗したツール実行のエラーメッセージ |
Stop
Agent がレスポンスを完了した後に発火します。Agent の停止をブロックできます。 品質ゲート、デスクトップ通知、ログ記録に使用します。
matcher: なし。Agent が停止するたびにこのイベントが発火します。
追加入力フィールド:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "Stop",
"stop_hook_active": false
}
| フィールド | 型 | 説明 |
|---|
stop_hook_active | boolean | 前回の Stop Hook ブロック後に Agent がリトライしている場合に true。スクリプトはこのフィールドを確認し、true の場合は exit 0 して無限ループを防止する必要があります。 |
Agent の停止をブロック: exit code 2 で、ブロック理由がユーザーメッセージとして会話に注入され、Agent は作業を続行します。
stdout JSON 出力フィールド(exit 0 時):
{
"decision": "block",
"reason": "Tests failing. Fix them before completing."
}
| フィールド | 型 | 説明 |
|---|
decision | string | "block"(停止をブロックし、Agent に作業を続行させる) |
reason | string | ブロックの理由。会話にメッセージとして注入される |
無限ループの防止: Stop Hook が Agent をブロック(exit 2)すると、Agent はリトライし、Stop イベントが stop_hook_active: true で再度発火します。スクリプトは必ずこのフィールドを確認し、true の場合は exit 0 してください。そうしないと Hook が無限にブロックし続けます。
SessionStart
セッション開始時に発火します。
matcher: セッションソース
| matcher 値 | 発火シナリオ |
|---|
startup | 新しいセッションの開始 |
resume | 既存セッションの再開 |
compact | コンテキスト圧縮完了後 |
追加入力フィールド:
{
"source": "startup",
"agent_type": "coder",
"model": ""
}
SessionEnd
セッション終了時に発火します。
matcher: 終了理由
| matcher 値 | 発火シナリオ |
|---|
prompt_input_exit | ユーザーが入力を終了(Ctrl+D 等) |
other | その他の理由 |
追加入力フィールド:
{
"reason": "prompt_input_exit"
}
SubagentStart / SubagentStop
サブ Agent の起動・完了時に発火します。SubagentStop は Stop と同様、サブ Agent の停止をブロックできます。
matcher: Agent タイプ名
SubagentStart 追加入力フィールド:
{
"agent_id": "83f488ef",
"agent_type": "general-purpose"
}
SubagentStop 追加入力フィールド:
{
"agent_id": "83f488ef",
"agent_type": "general-purpose",
"agent_transcript_path": "",
"stop_hook_active": false
}
| フィールド | 型 | 説明 |
|---|
agent_id | string | サブ Agent ID |
agent_type | string | サブ Agent タイプ(例:"general-purpose") |
agent_transcript_path | string | サブ Agent の Transcript ファイルのパス(SubagentStop のみ) |
stop_hook_active | boolean | 前回の SubagentStop ブロック後のリトライかどうか(SubagentStop のみ) |
PreCompact
コンテキスト圧縮前に発火します。
matcher: トリガー方法
| matcher 値 | 発火シナリオ |
|---|
manual | ユーザーが手動で /compact を実行 |
auto | コンテキストウィンドウが満杯になった時に自動トリガー |
追加入力フィールド:
{
"trigger": "manual",
"custom_instructions": "すべてのツール呼び出し結果を保持"
}
Notification
通知イベント発火時(権限リクエスト、タスク完了等)。
matcher: 通知タイプ
| matcher 値 | 発火シナリオ |
|---|
permission | 権限リクエスト通知 |
result | Agent の結果通知 |
追加入力フィールド:
{
"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"}
}
シナリオ例
危険なコマンドのブロック
スクリプト ~/.qoderwork/hooks/block-dangerous.sh:
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')
if echo "$command" | grep -qE 'rm\s+-rf|DROP\s+TABLE|mkfs|dd\s+if='; then
echo "危険なコマンドをブロックしました: $command" >&2
exit 2
fi
exit 0
設定:イベント PreToolUse、matcher Bash、command ~/.qoderwork/hooks/block-dangerous.sh。
ファイル書き込み後の自動 Lint
Agent がファイルを書き込み・編集するたびに、自動で lint を実行します。
スクリプト ~/.qoderwork/hooks/auto-lint.sh:
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
# Only lint JS/TS files
case "$file_path" in
*.js|*.ts|*.jsx|*.tsx)
npx eslint "$file_path" --fix 2>/dev/null
;;
esac
exit 0
設定:イベント PostToolUse、matcher Write|Edit、command ~/.qoderwork/hooks/auto-lint.sh。
デスクトップ通知
Agent の結果や権限リクエスト時にデスクトップ通知を表示します。
スクリプト ~/.qoderwork/hooks/notify.sh(macOS):
#!/bin/bash
input=$(cat)
message=$(echo "$input" | jq -r '.message // "タスク完了"' | head -c 100)
title=$(echo "$input" | jq -r '.title // "QoderWork"')
osascript -e "display notification \"$message\" with title \"$title\""
exit 0
設定:イベント Notification、matcher なし、command ~/.qoderwork/hooks/notify.sh。
Prompt コンテンツスクリーニング
スクリプト ~/.qoderwork/hooks/check-prompt.sh:
#!/bin/bash
input=$(cat)
prompt=$(echo "$input" | jq -r '.prompt')
if echo "$prompt" | grep -qiE '(password|secret|api_key|token)\s*[:=]\s*\S+'; then
echo "Prompt に機密情報が含まれている可能性があります。確認して再送信してください。" >&2
exit 2
fi
exit 0
設定:イベント UserPromptSubmit、matcher なし、command ~/.qoderwork/hooks/check-prompt.sh。
ツール失敗時のログ記録
スクリプト ~/.qoderwork/hooks/log-failure.sh:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
error=$(echo "$input" | jq -r '.error')
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $tool_name 失敗: $error" >> ~/.qoderwork/hooks/failure.log
exit 0
設定:イベント PostToolUseFailure、matcher なし、command ~/.qoderwork/hooks/log-failure.sh。
Agent を継続させる
Agent が停止する際に未完了タスクがないかチェックし、コミットされていない git 変更がある場合は Agent の停止をブロックします。
スクリプト ~/.qoderwork/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 ~/.qoderwork/hooks/check-continue.sh。
Prompt 送信時にコンテキストを注入
ユーザーの各プロンプト送信前に、現在の git ブランチ情報を Agent コンテキストとして自動注入します。
スクリプト ~/.qoderwork/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 ~/.qoderwork/hooks/inject-branch.sh。
機密情報を含む Prompt のブロック
パスワードやシークレットキーなどの機密キーワードを含む Prompt を検出してブロックします。
スクリプト ~/.qoderwork/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 ~/.qoderwork/hooks/block-sensitive.sh。
完全な設定例
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/check-prompt.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/block-dangerous.sh"
}
]
}
],
"PostToolUseFailure": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/log-failure.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/notify-done.sh"
}
]
}
]
}
}
注意事項
- タイムアウト処理: Hook スクリプトのデフォルトタイムアウトは 30 秒です。タイムアウト後、スクリプトは終了され、許可(続行)として扱われます。カスタムタイムアウトは将来のリリースでサポート予定です。
- エラー処理: スクリプトが予期しないコード(0 と 2 以外)で終了した場合、エラーメッセージはユーザーに表示されますが、Agent は中断されずに続行します。
- スクリプト権限: スクリプトに実行権限があることを確認してください(
chmod +x)。
- jq 依存: サンプルスクリプトは JSON 解析に
jq を使用しています。システムにインストールされていることを確認してください(macOS: brew install jq、Linux: apt install jq)。
- 再起動が必要:
~/.qoderwork/settings.json を変更した後、QoderWork を再起動して変更を適用してください。