Claude Media
Agent Teams Hooksで強制する品質ゲートの仕組み

Agent Teams Hooksで強制する品質ゲートの仕組み

TeammateIdle/TaskCreated/TaskCompletedの3イベントで、テスト未通過のタスクを完了扱いにさせない品質ゲートの組み方を解説します。

はじめに — 何を学ぶか

Agent Teamsには、teammateやタスクのライフサイクルに合わせてコマンドを自動実行できる専用のフックイベントが3つあります。TeammateIdleTaskCreatedTaskCompletedです。これらを使うと、「テストが通っていないのにタスクを完了扱いにする」「命名規則を無視したタスクが作られる」といった事態を、Claudeの判断任せではなく決定論的に止められます。

Claude Code Hooks完全ガイドではHooks全体の仕組みを扱っていますが、この3イベントはAgent Teams専用で、通常の単一セッションでは発火しません。本記事ではこの3イベントの入力スキーマと制御方法、そして「テストが通るまでタスクを完了させない」設定を実際に組む手順をまとめます。

前提として、Agent Teamsが有効化されていること(CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)。Hooksの基本的な設定ファイルの場所(.claude/settings.json)を理解していること。

Agent Teams専用の3イベント

まず3つのイベントがそれぞれ何をトリガーに発火するかを押さえます。

イベント発火タイミングブロックすると何が起きるか
TeammateIdle発火タイミングteammateがターンを終えてアイドルになる直前ブロックすると何が起きるかteammateはアイドルにならず作業を継続する
TaskCreated発火タイミングTaskCreateツールでタスクが作成されるときブロックすると何が起きるかタスク作成がロールバックされる
TaskCompleted発火タイミングタスクがcompleted状態にマークされるときブロックすると何が起きるかタスクは完了扱いにならない

TaskCreatedはTaskツールを持たないセッションでは発火しません。また3イベントともmatcherをサポートせず、条件分岐なしに毎回必ず発火します。

Step 1 — TeammateIdleでビルド成果物の存在を確認する

teammateが「作業を終えた」と判断してアイドルになる直前に、成果物が実際に存在するかを確認するフックです。

#!/bin/bash
 
if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi
 
exit 0

TeammateIdleフックの入力には、共通フィールドに加えてteammate_nameteam_nameが渡されます。制御方法は2通りです。

  • exit code 2: teammateはstderrのメッセージをフィードバックとして受け取り、アイドルにならず作業を続ける
  • JSON {"continue": false, "stopReason": "..."}: Stopフックと同様にteammateを完全に停止させる。stopReasonはユーザーに表示される

settings.jsonへの登録は次の形です。

{
  "hooks": {
    "TeammateIdle": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-build-artifact.sh"
          }
        ]
      }
    ]
  }
}

Step 2 — TaskCreatedでタスクの命名規則を強制する

タスクが作成されるタイミングで、タイトルの形式を検証します。チケット番号のないタスクを作らせたくない場合の例です。

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
 
if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi
 
exit 0

TaskCreatedフックの入力にはtask_idtask_subjectに加え、task_descriptionteammate_nameteam_nameが任意で含まれます。ブロック方法は2通りありますが、どちらを使ってもClaude Codeはタスクを削除し、あなたのメッセージをツールのエラーとしてClaudeに返します。

  • exit code 2: stderrのテキストがそのままメッセージになる
  • JSON {"decision": "block", "reason": "..."}: reasonがメッセージになる

TaskCreatedだけはcontinue: falseを無視する点に注意してください。ブロックしてもClaudeはタスクを作り直して作業を続けます。

Step 3 — TaskCompletedでテスト未通過のタスクを完了させない

もっとも実用性が高いのがこのフックです。タスクが完了マークされる直前にテストスイートを実行し、失敗していれば完了をブロックします。

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
 
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi
 
exit 0

TaskCompletedは2つの状況で発火します。任意のエージェントがTaskUpdateツールで明示的にタスクを完了させたとき、そしてteammateが未完了タスクを抱えたままターンを終えたときです。制御方法もこの2つの状況で挙動が変わります。

  • exit code 2: タスクは完了扱いにならず、stderrのメッセージがモデルへのフィードバックとして返る
  • JSON {"continue": false, "stopReason": "..."}: teammateがターンを終えたことでこのイベントが発火した場合はStopフック同様にteammateを完全停止させる。TaskUpdateツールが発火させた場合、Claude Codeはcontinue: falseを無視する(exit code 2は引き続きブロックとして働く)

settings.jsonへの登録は他の2イベントと同じ形です。TeammateIdleTaskCreatedTaskCompletedのキーごとにフック配列を並べれば、3つの品質ゲートを同時に運用できます。

{
  "hooks": {
    "TaskCreated": [
      { "hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/validate-task-title.sh" }] }
    ],
    "TaskCompleted": [
      { "hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-before-complete.sh" }] }
    ],
    "TeammateIdle": [
      { "hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-build-artifact.sh" }] }
    ]
  }
}

よくあるつまずき

  • スクリプトに実行権限を付け忘れる: chmod +xしていないと、本来ブロックしたい場面でフックが何もせず素通りしてしまいます。単一セッション用のサブエージェントhookと同じ落とし穴です
  • TaskCreatedcontinue: falseを返しても効かない: このイベントはexit code 2かdecision: "block"でしか止められません。continue: falseは無視される仕様です
  • TaskCompletedのブロックが効いていないように見える: TaskUpdateツール経由で発火したときはcontinue: falseが無視されます。teammateのターン終了時に発火したケースと挙動が違うことを忘れると、「ブロックしたはずなのに完全停止しない」と混乱します
  • 重いテストをTaskCompletedに直結させて全体が遅くなる: すべてのタスク完了で毎回フルテストスイートを回すと、taskの粒度が細かいチームほど待ち時間が積み上がります。対象範囲を絞ったテストコマンドに差し替えるほうが実用的です
  • フックスクリプトの標準出力にログを混ぜてしまう: exit code 2で返すメッセージはstderrに書く前提です。デバッグ用のechoをstdoutに残したままにすると、意図しないテキストがモデルへのフィードバックに紛れ込みます

3イベントを組み合わせたときの実際の流れ

3つのフックを同時に設定すると、teammateの1サイクルはおおむね次のように進みます。

まずteammateがタスクに着手する前、TaskCreatedフックがタイトルの形式を検証します。フォーマットに違反していればその場でタスクが削除され、Claudeは正しい形式でタスクを作り直してから作業を始めます。

作業が終わってteammateがTaskUpdateツールでタスクを完了にしようとすると、今度はTaskCompletedフックが走ります。テストが失敗していれば完了がブロックされ、teammateはテストを直すところからやり直します。

すべてのタスクを片付けてteammateが手を止めようとする最後の瞬間にはTeammateIdleフックが走り、ビルド成果物が実際に存在するかを最終確認します。

どこで止まったかが一目で分かります。この3段構えの利点は、どの段階で品質基準を満たしていないかが自動的に切り分けられることです。タスクの作り方がおかしいのか、個々のタスクの完了基準を満たしていないのか、チーム全体としての最終成果物が欠けているのか。teammateへの指示文でこれを毎回言い聞かせる必要がなく、決定論的に同じ基準が適用され続けます。

品質ゲートの粒度をどう決めるか

3つのイベントに何を割り当てるかは、チェックしたい対象の粒度で決めるとよいでしょう。

チェックしたい対象割り当てるイベント理由
タスクの命名・記述ルール割り当てるイベントTaskCreated理由タスクが作られる瞬間にしか検証できない
個々のタスクの完了基準(テスト・lint)割り当てるイベントTaskCompleted理由タスク単位の粒度で検証するのに最も自然
チーム全体としての最終成果物割り当てるイベントTeammateIdle理由teammateが本当に「終わった」と言えるかの最終ゲート

役割が違います。TaskCompletedTeammateIdleを両方使うと一見冗長に見えますが、TaskCompletedは個々のタスクの粒度でテストを通す規律を強制し、TeammateIdleはteammateが複数のタスクをまたいで全体の成果物(ビルド・デプロイ設定ファイル等)を仕上げているかを確認します。片方だけでは「タスクは全部テスト付きで完了しているのに、最終的な成果物一式が揃っていない」という抜け穴を防げません。逆にTeammateIdleだけを設定してテスト実行を最終確認一発に任せてしまうと、どのタスクが原因で成果物が欠けているのかを後から特定する手間が増えます。粒度の細かいTaskCompletedで早期に潰しておくほうが、手戻りのコストは小さく済みます。

単一セッションのサブエージェントhookとの違い

Sub-agentsにも、個別のサブエージェント定義にPreToolUseフックを埋め込んでツール実行を制限する仕組みがあります。たとえば読み取り専用のDBクエリしか許可しないサブエージェントを、frontmatterに直接hooksを書いて作ることができます。

これはサブエージェント1体に閉じた制御であるのに対し、TeammateIdleTaskCreatedTaskCompletedはチーム全体のタスクライフサイクルにかかる制御です。個々のツール呼び出しを縛りたいならサブエージェント側のhooks、チーム全体で「このタスクは基準を満たさないと完了させない」という規律を敷きたいならAgent Teams専用の3イベント、と使い分けます。

よくある質問

これらのフックは通常のセッションでも発火するか

TeammateIdleAgent Teamsが有効な場合にのみ意味を持つイベントです。TaskCreatedはTaskツールを持たないセッションでは発火しません。TaskCompletedはタスクが完了マークされるときに発火するイベントで、そもそもタスクを作らないセッションでは出番がありません。単一セッションでタスクツールを使わない場合、この3イベントはそもそも発火しません。

一時的にゲートを無効化したいときはどうするか

フックのスクリプト自体を書き換える必要はありません。.claude/settings.json側の該当イベントのエントリをコメントアウトするか、スクリプトの冒頭に環境変数で分岐する処理(例: [ "$SKIP_QUALITY_GATE" = "1" ] && exit 0)を仕込んでおけば、セッションの環境変数で一時的に無効化できます。

3つとも同時に設定する必要があるか

必須ではありません。命名規則だけ強制したいならTaskCreatedだけ、テストの完了基準だけ強制したいならTaskCompletedだけを設定しても機能します。小さく始められます。ただし前述のとおり、TaskCompletedTeammateIdleは検証する粒度が異なるため、両方を組み合わせるとタスク単位と成果物単位の両方の抜け穴を防げます。小さなチームから始めるなら、まずTaskCompletedだけを設定して効果を確認し、必要に応じて他の2つを足していくのが無理のない導入手順です。

まとめ

必要なのはこの3イベントだけです。Agent Teamsの品質ゲートはTeammateIdle(アイドル化の直前)にビルド成果物、TaskCreated(タスク作成時)に命名規則、TaskCompleted(タスク完了時)にテストを割り当てて組み立てます。もっとも実用性が高いのはTaskCompletedでのテスト実行で、テストが通らないタスクをチームに完了扱いさせない仕組みを機械的に強制できます。3イベントとも制御方法(exit codeとJSON出力)の細部が微妙に異なるため、どちらの状況で発火したかを意識して設計することが、意図通りに動くhookを書く近道です。

この記事を共有:XはてブLinkedIn