コンテンツにスキップ

GitHub Copilot Hooks完全ガイド:Coding Agent・CLIのワークフロー自動化

GitHub Copilot 完全ガイド

対象: 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 AgentGitHub 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ファイルを作成する。

.github/hooks/copilot-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
bashBash環境で実行するコマンド/スクリプトパスYes(bash環境)
powershellPowerShell環境で実行するコマンド/スクリプトパスYes(Windows環境)
cwdスクリプト実行時の作業ディレクトリNo
timeoutSecタイムアウト秒数(デフォルト30秒)No
comment用途の説明No

bashpowershellを並列定義することで、Linux/macOSとWindowsの両環境に対応できる。

stdin入力の共通フィールド

すべてのhookはJSON形式のデータをstdinで受け取る。

{
  "timestamp": 1704614400000,
  "cwd": "/path/to/project"
}

イベントタイプによって追加フィールドが付与される(toolNametoolArgsprompterrorなど)。詳細は後述の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の中核機能である。最も頻繁に使うパターンから見ていく。

.github/hooks/block-dangerous.sh
#!/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)

特定ディレクトリ以外の編集をブロックする。チーム開発で「触ってはいけない領域」を確定的に守れる。

.github/hooks/restrict-edit-scope.sh
#!/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作成そのものをブロックできる。

.github/hooks/pr-security-gate.sh
#!/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を配置して操作ログを網羅的に記録する。

.github/hooks/compliance-audit.json
{
  "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形式):

.github/hooks/audit/log-tool-result.sh
#!/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の無人実行を監視する際に有効だ。

.github/hooks/slack-error-notify.sh
#!/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 HooksClaude Code Hooks
設定ファイル.github/hooks/*.json~/.claude/settings.json / .claude/settings.json
設定形式JSON(versionキーあり)JSON(hooksキー直下)
イベント数6種14種
実行タイプcommand のみcommand / prompt / agent
ツール実行制御preToolUseで deny/allowPreToolUseで deny/allow + updatedInput
ツール入力修正非対応updatedInput で対応
matcherパターンなし(スクリプト内で判定)正規表現マッチャー
Prompt型hook非対応LLMによる判断が可能
OS対応bash + powershell の並列定義bashのみ
タイムアウトデフォルト30秒デフォルト60秒
非同期実行非対応async: true 対応
インタラクティブ管理なし/hooksコマンド

イベントの対応関係

GitHub CopilotClaude Code備考
sessionStartSessionStart機能的にほぼ同等
sessionEndSessionEnd機能的にほぼ同等
userPromptSubmittedUserPromptSubmitClaude側はコンテキスト注入・ブロックに対応
preToolUsePreToolUseClaude側はmatcher + updatedInput + prompt型で高機能
postToolUsePostToolUseClaude側はadditionalContextで追加コンテキスト注入可能
errorOccurred(対応なし)Copilot固有
(対応なし)PermissionRequest権限ダイアログの自動応答
(対応なし)Stop / SubagentStop停止判定の制御
(対応なし)SubagentStartサブエージェント監視
(対応なし)PreCompactコンパクト前処理
(対応なし)Notification通知イベント制御
(対応なし)TeammateIdle / TaskCompletedAgent 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スクリプトに渡す。

.github/copilot-setup-steps.yml
# 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が動作しない場合

  1. 設定ファイルが.github/hooks/に配置されているか確認
  2. Coding Agentの場合、設定ファイルがデフォルトブランチにマージ済みか確認
  3. スクリプトに実行権限(chmod +x)が付与されているか確認
  4. 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と同じくらい当然のリポジトリ標準装備になるだろう。

関連記事


この記事は2026年2月27日時点の情報に基づいています。最新の仕様はGitHub Copilot公式ドキュメントをご確認ください。