GitHub Copilot Hooks完全ガイド:Coding Agent・CLIのワークフロー自動化¶
対象: Copilot Coding AgentまたはCLIでワークフロー自動化を組みたい開発者・チームリード
この記事のポイント¶
共通仕様のHooks基盤
Coding AgentとCopilot CLIの両方で
.github/hooks/*.jsonの同一設定が動作するpreToolUseによる確定的制御
ツール実行を
allow/denyでプログラム的に制御。危険操作のブロックやコーディング規約の強制を確実に実行6種のライフサイクルイベント
セッション開始からエラー発生まで、エージェントの全動作ポイントにフックを挿入可能
Claude Code Hooksとの明確な使い分け
設定形式・イベント数・実行モデルの差異を踏まえた併用戦略を提示
はじめに¶
rm -rf / をエージェントが実行しようとしたら? npm publish を本番ブランチで勝手に走らせたら? ——Custom InstructionsやAGENTS.mdでは「お願い」しかできない。Hooksなら止められる。
GitHub Copilot Hooksは、Coding AgentとCopilot CLIに共通するワークフロー自動化機能である。エージェントのセッション開始からツール実行、エラー発生に至るまでの各ポイントで、カスタムシェルコマンドを確定的に実行する。条件に合致すれば必ず発火する点が、プロンプト指示との本質的な違いだ。
本記事では、Copilot Hooksの仕様・設定方法・実装パターンを解説し、Claude Code Hooksとの比較を通じて使い分けの指針を示す1。
まずはHooksがどの環境で動き、どのプランで使えるのかを整理する。
Copilot Hooksの全体像¶
対応プラットフォーム¶
Copilot Hooksは2つの環境で動作する。設定ファイルの形式は共通である。
| プラットフォーム | 動作環境 | 設定ファイルの読み込み元 |
|---|---|---|
| Coding Agent | GitHub Actions上のサンドボックス | リポジトリのデフォルトブランチ |
| Copilot CLI | ローカルターミナル | カレントディレクトリ |
Coding Agentではデフォルトブランチ上に設定ファイルが存在する必要がある。CLIではカレントディレクトリの.github/hooks/*.jsonが参照される。
つまり、1つの設定ファイルをリポジトリにコミットすれば、Coding AgentでもCLIでもそのまま動く。
対応プラン¶
以下のCopilotプランで利用可能である。
- Copilot Pro / Pro+(個人)
- Copilot Business(組織)
- Copilot Enterprise(企業)
Freeプランでは利用できない2。
環境とプランを確認したところで、Hooksが提供する6種のイベントを見ていく。
6つのHookイベントタイプ¶
エージェントのライフサイクル全体を6種のイベントでカバーする。
| イベント | 発火タイミング | 出力の処理 | 主な用途 |
|---|---|---|---|
| sessionStart | セッション開始・再開時 | 無視 | 環境初期化、監査ログ開始 |
| sessionEnd | セッション終了時 | 無視 | クリーンアップ、レポート生成 |
| userPromptSubmitted | プロンプト送信時 | 無視 | リクエストログ、使用量分析 |
| preToolUse | ツール実行前 | 承認/拒否を制御可能 | 危険操作ブロック、セキュリティ強制 |
| postToolUse | ツール実行後 | 無視 | 統計収集、失敗アラート |
| errorOccurred | エラー発生時 | 無視 | エラーログ、通知 |
この中でpreToolUseだけが特別だ。JSON出力でpermissionDecisionを返すことにより、ツール実行をプログラム的に承認・拒否できる1。残りの5イベントはログ記録・通知・集計など副作用用途が中心であり、エージェントの動作をブロックする能力は持たない。
まずpreToolUseを押さえれば、Hooksの実用的な価値の大半をカバーできる。では、実際にどうやって設定するのか。
設定ガイド¶
基本構成¶
.github/hooks/ディレクトリにJSONファイルを作成する。
{
"version": 1,
"hooks": {
"sessionStart": [
{
"type": "command",
"bash": "./scripts/session-start.sh",
"powershell": "./scripts/session-start.ps1",
"timeoutSec": 30
}
],
"preToolUse": [
{
"type": "command",
"bash": "./.github/hooks/security-check.sh"
}
],
"postToolUse": [
{
"type": "command",
"bash": "./.github/hooks/audit-log.sh"
}
]
}
}
各hookオブジェクトのプロパティは以下の通り。
| プロパティ | 説明 | 必須 |
|---|---|---|
type | "command" のみ | Yes |
bash | Bash環境で実行するコマンド/スクリプトパス | Yes(bash環境) |
powershell | PowerShell環境で実行するコマンド/スクリプトパス | Yes(Windows環境) |
cwd | スクリプト実行時の作業ディレクトリ | No |
timeoutSec | タイムアウト秒数(デフォルト30秒) | No |
comment | 用途の説明 | No |
bashとpowershellを並列定義することで、Linux/macOSとWindowsの両環境に対応できる。
stdin入力の共通フィールド¶
すべてのhookはJSON形式のデータをstdinで受け取る。
{
"timestamp": 1704614400000,
"cwd": "/path/to/project"
}
イベントタイプによって追加フィールドが付与される(toolName、toolArgs、prompt、errorなど)。詳細は後述のHook入出力リファレンスを参照。
複数hookの順序実行¶
同じイベントに複数のhookを定義できる。配列の順序で逐次実行される。
{
"preToolUse": [
{ "type": "command", "bash": "./scripts/security-check.sh", "comment": "セキュリティ検証(1番目)" },
{ "type": "command", "bash": "./scripts/audit-log.sh", "comment": "監査ログ(2番目)" },
{ "type": "command", "bash": "./scripts/metrics.sh", "comment": "メトリクス収集(3番目)" }
]
}
1番目のhookがdenyを返した場合、2番目以降は実行されずツール呼び出しがブロックされる。
設定の仕組みがわかったところで、具体的な実装パターンに入る。
実践的な使用例¶
例1: 危険コマンドのブロック(preToolUse)¶
preToolUseはCopilot Hooksの中核機能である。最も頻繁に使うパターンから見ていく。
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TOOL_ARGS=$(echo "$INPUT" | jq -r '.toolArgs')
# bashコマンド以外は許可
if [ "$TOOL_NAME" != "bash" ]; then
exit 0
fi
# 危険なパターンを検出
COMMAND=$(echo "$TOOL_ARGS" | jq -r '.command')
if echo "$COMMAND" | grep -qE '(rm -rf /|sudo|mkfs|DROP TABLE|format)'; then
jq -n \
--arg reason "Dangerous command detected: $COMMAND" \
'{permissionDecision: "deny", permissionDecisionReason: $reason}'
exit 0
fi
# デフォルトで許可
echo '{"permissionDecision":"allow"}'
permissionDecisionの値は3種類。
"allow"— ツール実行を許可"deny"— ツール実行をブロック(permissionDecisionReasonがエージェントに通知される)"ask"— ユーザーに確認を求める
denyを返すとエージェントは代替手段を探すか、ユーザーに報告する。プロンプトの「やらないで」ではなく、コードレベルで確実に止まる。
例2: ファイル編集範囲の制限(preToolUse)¶
特定ディレクトリ以外の編集をブロックする。チーム開発で「触ってはいけない領域」を確定的に守れる。
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
# editツールのみチェック
if [ "$TOOL_NAME" = "edit" ] || [ "$TOOL_NAME" = "create" ]; then
FILE_PATH=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.path')
if [[ ! "$FILE_PATH" =~ ^(src/|test/|docs/) ]]; then
jq -n '{permissionDecision: "deny", permissionDecisionReason: "Can only edit files in src/, test/, or docs/ directories"}'
exit 0
fi
fi
# その他のツールはすべて許可
exit 0
例3: PR差分の静的セキュリティスキャン(Coding Agent固有・preToolUse)¶
Coding Agentはプルリクエストを自律的に作成する。この性質を逆用し、Agentがbashでgh pr diffを呼ぼうとした瞬間に差分をインターセプトして静的解析にかけ、セキュリティスコアが閾値を下回ればPR作成そのものをブロックできる。
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
COMMAND=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.command // empty')
# PR作成コマンド(gh pr create)のみ対象
if [ "$TOOL_NAME" != "bash" ] || ! echo "$COMMAND" | grep -q 'gh pr create'; then
exit 0
fi
# 差分を取得して静的解析スクリプトに渡す
DIFF=$(git diff origin/main --name-only 2>/dev/null)
SECURET_COUNT=$(echo "$DIFF" | xargs grep -rn \
-e 'password\s*=' \
-e 'secret\s*=' \
-e 'api_key\s*=' \
-e 'token\s*=' 2>/dev/null | wc -l)
if [ "$SECERET_COUNT" -gt 0 ]; then
jq -n \
--arg reason "Security scan failed: ${SECERET_COUNT} potential secret(s) detected in diff. Review before creating PR." \
'{permissionDecision: "deny", permissionDecisionReason: $reason}'
exit 0
fi
echo '{"permissionDecision":"allow"}'
CLIのローカル実行ではgh pr createを意識的に使うが、Coding Agentではエージェントが自律判断でPRを作成しにいく。このhookが刺さるのはまさにその瞬間だ。Custom Instructionsで「秘密情報を含むPRを作らないで」と書くのとは確実性が桁違いになる。
ここまでの3例は全てpreToolUseだ。実務ではpreToolUseが最も利用頻度が高い。では、他の5イベントはどう使うのか。
例4: コンプライアンス監査ログ(全イベント)¶
エンタープライズ環境でのコンプライアンス要件に対応する場合、全イベントにhookを配置して操作ログを網羅的に記録する。
{
"version": 1,
"hooks": {
"sessionStart": [{ "type": "command", "bash": "./.github/hooks/audit/log-session-start.sh" }],
"userPromptSubmitted": [{ "type": "command", "bash": "./.github/hooks/audit/log-prompt.sh" }],
"preToolUse": [{ "type": "command", "bash": "./.github/hooks/audit/log-tool-use.sh" }],
"postToolUse": [{ "type": "command", "bash": "./.github/hooks/audit/log-tool-result.sh" }],
"errorOccurred": [{ "type": "command", "bash": "./.github/hooks/audit/log-error.sh" }],
"sessionEnd": [{ "type": "command", "bash": "./.github/hooks/audit/log-session-end.sh" }]
}
}
postToolUseのログスクリプト例(JSON Lines形式):
#!/bin/bash
INPUT=$(cat)
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
RESULT_TYPE=$(echo "$INPUT" | jq -r '.toolResult.resultType')
jq -n \
--arg ts "$TIMESTAMP" \
--arg tool "$TOOL_NAME" \
--arg result "$RESULT_TYPE" \
'{timestamp: $ts, tool: $tool, result: $result}' >> logs/audit.jsonl
例5: Slack通知連携(errorOccurred)¶
エラー発生時にSlackへ自動通知する。Coding Agentの無人実行を監視する際に有効だ。
#!/bin/bash
INPUT=$(cat)
ERROR_MSG=$(echo "$INPUT" | jq -r '.error.message')
ERROR_NAME=$(echo "$INPUT" | jq -r '.error.name')
WEBHOOK_URL="${SLACK_WEBHOOK_URL}"
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg msg "[$ERROR_NAME] $ERROR_MSG" '{text: ("🚨 Agent Error: " + $msg)}')"
Webhook URLの管理
Webhook URLはスクリプトにハードコードせず、環境変数やGitHub Secretsから取得すること。
ここまでの実装パターンで使ったstdin入力のスキーマを、イベント別に整理しておく。
Hook入出力リファレンス¶
各イベントのstdin入力スキーマをまとめる。スクリプト実装時の参照用。
preToolUse 入力/出力
入力(stdin)
{
"timestamp": 1704614600000,
"cwd": "/path/to/project",
"toolName": "bash",
"toolArgs": "{\"command\":\"npm test\",\"description\":\"Run tests\"}"
}
toolNameの主な値: bash, edit, create, view, grep, glob
出力(JSON)
{
"permissionDecision": "allow",
"permissionDecisionReason": "Safe read operation"
}
postToolUse 入力
{
"timestamp": 1704614700000,
"cwd": "/path/to/project",
"toolName": "bash",
"toolArgs": "{\"command\":\"npm test\"}",
"toolResult": {
"resultType": "success",
"textResultForLlm": "All tests passed (15/15)"
}
}
resultTypeの値: "success", "failure", "denied"
sessionStart 入力
{
"timestamp": 1704614400000,
"cwd": "/path/to/project",
"source": "new",
"initialPrompt": "Create a new feature"
}
sourceの値: "new", "resume", "startup"
sessionEnd 入力
{
"timestamp": 1704618000000,
"cwd": "/path/to/project",
"reason": "complete"
}
reasonの値: "complete", "error", "abort", "timeout", "user_exit"
errorOccurred 入力
{
"timestamp": 1704614800000,
"cwd": "/path/to/project",
"error": {
"message": "Network timeout",
"name": "TimeoutError",
"stack": "TimeoutError: Network timeout\n at ..."
}
}
仕様を把握したところで、同じ「Hooks」という名前を持つClaude Codeの仕組みと比較してみる。
Claude Code Hooksとの比較¶
SmartScopeではClaude Code Hooks完全ガイドも公開している。両者を比較することで、プロジェクト要件に応じた適切な選択が可能になる。
設計思想の違い¶
GitHub Copilot Hooksはリポジトリに紐づくチーム共有の自動化基盤である。.github/hooks/に配置してデフォルトブランチにマージすれば、Coding AgentとCLIの両方で自動的に有効化される。この設計はGitHub ActionsのYAMLワークフローと同じ思想だ——リポジトリがポリシーの境界単位であり、コードとhookの設定が同じgitの履歴で管理される。hookの変更はコードレビューの流れに乗り、差し戻しもgit revert一発で完結する。
対してClaude Code Hooksは個人・プロジェクト・管理者の3階層設定を持つ。これはClaude Codeがローカルファーストのツールとして設計されたことに由来する。~/.claude/settings.json(個人)と.claude/settings.json(プロジェクト)の分離により、「会社の監査要件はグローバルで適用しつつ、フォーマッタの設定はリポジトリごとに変える」という細粒度の制御が可能だ。管理者ポリシーによる強制配布は後付けで追加された機能であり、根底にある設計は個人カスタマイズ優先である。
一言で整理すると、Copilotは「チームのgitリポジトリを信頼の起点にする」設計、Claude Codeは「ユーザーのローカル環境を起点にしつつ組織制御を重ねる」設計だ。前者はCI/CDとの統合が自然で、後者は開発者体験の細かなチューニングに向いている。
機能比較¶
| 項目 | GitHub Copilot Hooks | Claude Code Hooks |
|---|---|---|
| 設定ファイル | .github/hooks/*.json | ~/.claude/settings.json / .claude/settings.json |
| 設定形式 | JSON(versionキーあり) | JSON(hooksキー直下) |
| イベント数 | 6種 | 14種 |
| 実行タイプ | command のみ | command / prompt / agent |
| ツール実行制御 | preToolUseで deny/allow | PreToolUseで deny/allow + updatedInput |
| ツール入力修正 | 非対応 | updatedInput で対応 |
| matcherパターン | なし(スクリプト内で判定) | 正規表現マッチャー |
| Prompt型hook | 非対応 | LLMによる判断が可能 |
| OS対応 | bash + powershell の並列定義 | bashのみ |
| タイムアウト | デフォルト30秒 | デフォルト60秒 |
| 非同期実行 | 非対応 | async: true 対応 |
| インタラクティブ管理 | なし | /hooksコマンド |
イベントの対応関係¶
| GitHub Copilot | Claude Code | 備考 |
|---|---|---|
| sessionStart | SessionStart | 機能的にほぼ同等 |
| sessionEnd | SessionEnd | 機能的にほぼ同等 |
| userPromptSubmitted | UserPromptSubmit | Claude側はコンテキスト注入・ブロックに対応 |
| preToolUse | PreToolUse | Claude側はmatcher + updatedInput + prompt型で高機能 |
| postToolUse | PostToolUse | Claude側はadditionalContextで追加コンテキスト注入可能 |
| errorOccurred | (対応なし) | Copilot固有 |
| (対応なし) | PermissionRequest | 権限ダイアログの自動応答 |
| (対応なし) | Stop / SubagentStop | 停止判定の制御 |
| (対応なし) | SubagentStart | サブエージェント監視 |
| (対応なし) | PreCompact | コンパクト前処理 |
| (対応なし) | Notification | 通知イベント制御 |
| (対応なし) | TeammateIdle / TaskCompleted | Agent Teams連携 |
使い分けの判断基準¶
Copilot Hooksが適するケース:
- GitHub上のCoding Agentを中心にワークフローを組んでいる
- チーム全体で統一的なセキュリティポリシー・監査ログを適用したい
- Windows環境(PowerShell対応が必要)
- hookの設定をリポジトリにコミットしてバージョン管理したい
Claude Code Hooksが適するケース:
- ローカル開発環境でのリアルタイムな品質制御が主目的
- LLMベースの判断(Prompt hook)やサブエージェント制御が必要
- ツール入力の動的修正(updatedInput)を活用したい
- Plugin経由でのhook共有・配布を行いたい
両者を併用するケース:
GitHub上でのCoding Agent作業にはCopilot Hooksで監査ログ・セキュリティ制御を適用し、ローカルのClaude Code作業にはClaude Code Hooksでフォーマット・品質チェックを適用する。共通のセキュリティスクリプトを.github/hooks/と.claude/hooks/にそれぞれ配置することで、環境を問わず同じ安全基準を維持できる。
どちらを選ぶにせよ、hookスクリプト自体の品質がそのまま信頼性に直結する。最後に実装上の注意点をまとめる。
スクリプトのベストプラクティス¶
入力の読み取りとパース¶
#!/bin/bash
# stdinからJSON入力を読み取り
INPUT=$(cat)
# jqでフィールドを抽出
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
PowerShellの場合:
$input = [Console]::In.ReadToEnd() | ConvertFrom-Json
$toolName = $input.toolName
$timestamp = $input.timestamp
JSON出力の構築¶
# ❌ 文字列連結(特殊文字でパースエラーの原因に)
echo "{\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"$REASON\"}"
# ✅ jq -n(安全にJSON化)
REASON="Path traversal detected"
jq -n --arg reason "$REASON" '{permissionDecision: "deny", permissionDecisionReason: $reason}'
jq -nを使えば、特殊文字のエスケープ漏れによるJSONパースエラーを防止できる。
パフォーマンス¶
Hooksは同期的に実行され、完了するまでエージェントの処理をブロックする。
- 実行時間は5秒以内を目安にする
- ファイルへのappend(非同期I/O)を優先する
- 重い処理はバックグラウンドプロセスに委譲する(
nohup ... &) - 計算結果のキャッシュを活用する
セキュリティ¶
- hook内で処理する入力は必ずバリデーション・サニタイズする
- コマンド構築時は適切なシェルエスケープを行う
- トークンやパスワードなどの機密情報をログに記録しない
- hookスクリプトとログファイルに適切なパーミッション(
chmod 700)を設定する
Coding Agent固有の注意点¶
CLIではターミナルの出力を直接確認できるが、Coding AgentはGitHub Actionsのサンドボックス上で無人実行される。この差異が実務上のハマりどころになる。
サンドボックス環境の制約¶
Coding Agentのサンドボックスには以下の制約がある。
- ネットワーク外向き通信は限定的。 Slack通知などの外部APIコールは、GitHub ActionsのIP許可設定を確認すること
- 書き込み可能なパスが制限される場合がある。 ログファイルのパスはリポジトリ内の相対パスを使い、絶対パスは避ける
- セッションをまたいでローカルファイルは引き継がれない。 キャッシュが必要な場合はActions Cacheを活用する
GitHub Secretsとの連携¶
Webhook URLやAPIキーはGitHub Secretsに格納し、Actions環境変数経由でhookスクリプトに渡す。
# Copilot Agentのセットアップステップで環境変数を設定
steps:
- name: Set hook environment
run: echo "SLACK_WEBHOOK_URL=${{ secrets.SLACK_WEBHOOK_URL }}" >> $GITHUB_ENV
hookスクリプトは$SLACK_WEBHOOK_URLを環境変数として参照する。スクリプト内にシークレットをハードコードすると、リポジトリの履歴に残るため絶対に避けること。
Actionsログでのデバッグ¶
Coding AgentがhookをどのようにFailさせたかはGitHub Actionsのジョブログで確認できる。stderrへの出力がそのままログに記録されるため、デバッグ用のecho ... >&2が有効だ。
# hookスクリプト内でActionsログに可視化する
echo "::debug::Processing tool: $TOOL_NAME" >&2
echo "::warning::Suspicious pattern detected in: $FILE_PATH" >&2
::debug::や::warning::のAnnotationフォーマットを使うと、ActionsのUI上でわかりやすく表示される。
トラブルシューティング¶
hookが動作しない場合¶
- 設定ファイルが
.github/hooks/に配置されているか確認 - Coding Agentの場合、設定ファイルがデフォルトブランチにマージ済みか確認
- スクリプトに実行権限(
chmod +x)が付与されているか確認 - JSONの構文エラーがないか検証(
jq . < hooks.json)
ローカルテスト¶
hookスクリプトはパイプでテスト入力を流すことでローカルで検証できる。
# テスト入力を作成してhookに流す
echo '{"timestamp":1704614400000,"cwd":"/tmp","toolName":"bash","toolArgs":"{\"command\":\"ls\"}"}' \
| ./.github/hooks/security-check.sh
# exit codeを確認
echo $?
# JSON出力を検証
./.github/hooks/security-check.sh < test-input.json | jq .
デバッグ用ログ出力¶
#!/bin/bash
INPUT=$(cat)
echo "DEBUG: Received input" >&2
echo "$INPUT" >&2
# ... 以降の処理
stderrへの出力はhookの処理結果に影響しないため、デバッグ情報の出力先として安全に使用できる。
まとめ¶
GitHub Copilot Hooksは、Coding AgentとCopilot CLIに共通するリポジトリベースのワークフロー自動化基盤だ。6種のイベントのうち、preToolUseによる承認・拒否が最も強力な制御手段である。
Claude Code Hooksとの比較では、イベント数やPrompt hookの有無でClaude Codeが先行する一方、Copilot Hooksはリポジトリにコミットするだけでチーム全体に即適用できるシンプルさを持つ。両者は競合ではなく補完関係にある。
Hooksの本質は「指示」から「強制」への転換だ。Custom Instructionsの「やらないで」とpreToolUseのdenyでは、確実性がまるで違う。Coding Agentが日常的にPRを作る時代には、「エージェントが何をしてよいか」をコードで定義するHooksが、Custom Instructionsと同じくらい当然のリポジトリ標準装備になるだろう。
関連記事¶
Hooks + Custom Instructionsで「指示」と「強制」を使い分ける
Skills × Hooksでエージェントの手順知識と品質制御を両立
14イベント・3ハンドラタイプのClaude Code側Hooksとの詳細比較
CopilotとClaude Codeの使い分け戦略
この記事は2026年2月27日時点の情報に基づいています。最新の仕様はGitHub Copilot公式ドキュメントをご確認ください。