CLAUDE LABEN
VERSION — 8月10日に v2.1.227 が出ていました。新機能はなく、プラン判定と CI まわりの不具合修正が中心ですAUTO — 8月14日の既定化まで残り2日です。Pro・Max・Team で Claude Code の auto モードが既定で有効になりますCI — claude-code-action で allowed_non_write_users を指定すると GitHub ホストランナー上の Bash が全て失敗していた問題が直りましたBILLING — 期限切れトークンで開始したセッションが加入プランを取り違え、Max 利用者に使用クレジットの有効化を促していた不具合も解消していますSUNSET — 旧 Workbench と実験的プロンプトツール API の廃止は8月17日で、残り5日ですPRICE — Sonnet 5 のプロモ価格 $2/$10 per Mtok は8月31日までで、9月1日から $3/$15 per Mtok になりますVERSION — 8月10日に v2.1.227 が出ていました。新機能はなく、プラン判定と CI まわりの不具合修正が中心ですAUTO — 8月14日の既定化まで残り2日です。Pro・Max・Team で Claude Code の auto モードが既定で有効になりますCI — claude-code-action で allowed_non_write_users を指定すると GitHub ホストランナー上の Bash が全て失敗していた問題が直りましたBILLING — 期限切れトークンで開始したセッションが加入プランを取り違え、Max 利用者に使用クレジットの有効化を促していた不具合も解消していますSUNSET — 旧 Workbench と実験的プロンプトツール API の廃止は8月17日で、残り5日ですPRICE — Sonnet 5 のプロモ価格 $2/$10 per Mtok は8月31日までで、9月1日から $3/$15 per Mtok になります
記事一覧/Claude Code
Claude Code/2026-06-14上級

サブエージェントの出力を採点し、不合格なら作り直させる SubagentStop フック

サブエージェントの成果物に時々ルール違反が混じる問題へ、SubagentStopフックで自動採点と再依頼を組み込みます。採点基準をJSONルーブリックに固定する理由、トランスクリプトを読む採点器の実装、無限ループを防ぐガード、運用して見えた実務的な勘所を動くコード付きで整理します。

Claude Code217サブエージェント13フック2品質ゲート4自動化77

サブエージェントに定型作業を任せると、9割はきれいに仕上がるのに、1割だけ約束したルールを外した成果物が混じる——これが一番厄介でした。個人開発で複数のサイト(Dolice Labs)を回している私の場合は、記事生成の下請けを別エージェントに切り出していますが、「禁止語が入る」「見出しの数が足りない」といった違反が、忙しい日ほど見逃されてマージされてしまいます。

手で全部レビューすれば防げますが、それでは委譲した意味がありません。そこで、サブエージェントが仕事を終えた瞬間に機械が採点し、基準を満たさなければ本人にやり直させる仕組みを SubagentStop フックで組みました。

SubagentStop はサブエージェントの「提出ボタン」に挟まる

Claude Code のフックには複数のイベントがありますが、サブエージェント専用の出口が SubagentStop です。親エージェントの停止に反応する Stop とは別物で、サブエージェントが応答を返し終えた直後にだけ発火します。ここがちょうど「提出物を受け取る前の検品ライン」になります。

重要なのは、このフックが終了コードと JSON 出力で挙動を制御できる点です。{"decision": "block", "reason": "..."} を標準出力に返すと、Claude Code はサブエージェントを止めずに、reason を指示として渡したまま処理を続行させます。つまり「ここがダメだから直して」とフィードバックを注入できます。採点結果をそのまま差し戻し文に使えるのが、この設計の肝です。

採点基準は散文ではなく JSON ルーブリックに固定する

「良い記事を書いて」のような曖昧な基準は機械採点に向きません。違反の有無を一意に判定できる形まで分解します。私が下請けの記事生成に使っているルーブリックはこんな形です。

{
  "min_h2": 4,
  "max_chars": 12000,
  "min_chars": 2500,
  "banned_words": ["崩壊", "最強", "爆速", "神", "完全ガイド", "決定版"],
  "forbidden_openers": ["この記事では", "本記事では", "いかがでしたか"],
  "require_code_block": true
}

ポイントは、各項目が「数えられる」か「文字列一致で判定できる」ことです。主観が入る評価(面白さ・読みやすさ)はここに入れません。あくまで「機械で確実に弾けるルール違反」だけを置き、創造的な品質は人間と本体プロンプト側に任せます。境界を引くことで、フックが誤検知でループに陥るのを防げます。

フック本体:標準入力の JSON からトランスクリプトを開く

SubagentStop フックは、標準入力に session 情報を JSON で受け取ります。その中の transcript_path がサブエージェントの会話ログ(JSONL)です。最後のアシスタント発言が提出物なので、そこを取り出して採点器に渡します。

#!/usr/bin/env bash
# .claude/hooks/grade-subagent.sh
set -euo pipefail
 
INPUT="$(cat)"                       # フックは stdin で JSON を受け取る
TRANSCRIPT="$(printf '%s' "$INPUT" | node -e \
  'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{
     console.log(JSON.parse(d).transcript_path || "")})')"
 
if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then
  exit 0                             # 採点対象がなければ素通り
fi
 
node "$(dirname "$0")/grade.mjs" "$TRANSCRIPT"

set -euo pipefail を付けているのは、採点器がエラーで落ちたときにフックが無言で成功扱いになるのを防ぐためです。検品ラインが壊れたまま全部合格になるのが、品質ゲートで一番怖い失敗です。

採点器:トランスクリプト末尾を読んでルーブリックと突き合わせる

採点本体は決定的(同じ入力なら同じ結果)に保ちます。外部 API を呼ばないので速く、無料で、ネットワーク障害でも止まりません。

// .claude/hooks/grade.mjs
import { readFileSync } from "node:fs";
 
const RUBRIC = JSON.parse(
  readFileSync(new URL("./rubric.json", import.meta.url), "utf8")
);
 
const transcriptPath = process.argv[2];
const lines = readFileSync(transcriptPath, "utf8").trim().split("\n");
 
// JSONL を末尾からたどり、最後のアシスタントのテキストを取り出す
let text = "";
for (let i = lines.length - 1; i >= 0; i--) {
  const ev = JSON.parse(lines[i]);
  if (ev.message?.role !== "assistant") continue;
  const blocks = ev.message.content ?? [];
  text = blocks.filter(b => b.type === "text").map(b => b.text).join("\n");
  if (text) break;
}
 
const fail = [];
const h2 = (text.match(/^##\s+/gm) ?? []).length;
if (h2 < RUBRIC.min_h2) fail.push(`H2 が ${h2} 個(最低 ${RUBRIC.min_h2} 個)`);
 
const chars = [...text].length;
if (chars < RUBRIC.min_chars) fail.push(`本文 ${chars} 字(最低 ${RUBRIC.min_chars} 字)`);
if (chars > RUBRIC.max_chars) fail.push(`本文 ${chars} 字(上限 ${RUBRIC.max_chars} 字)`);
 
for (const w of RUBRIC.banned_words)
  if (text.includes(w)) fail.push(`禁止語「${w}」を含む`);
 
for (const o of RUBRIC.forbidden_openers)
  if (text.includes(o)) fail.push(`定型表現「${o}」を含む`);
 
if (RUBRIC.require_code_block && !text.includes("```"))
  fail.push("コードブロックが無い");
 
if (fail.length === 0) process.exit(0);   // 合格:何も出力せず終了
 
// 不合格:block 判定を JSON で返し、理由を差し戻し指示にする
console.log(JSON.stringify({
  decision: "block",
  reason:
    "提出物が品質ルーブリックを満たしていません。次の点を直して再提出してください:\n" +
    fail.map(f => `- ${f}`).join("\n"),
}));
process.exit(0);

exit 0 のまま decision: block を返しているのがコツです。終了コード 2 でもブロックできますが、JSON で返すと reason をそのままサブエージェントへの指示として渡せるため、「何を直すか」が本人に伝わります。差し戻し文を箇条書きにしておくと、再生成の精度が目に見えて上がりました。

無限ループを防ぐガードを必ず入れる

この仕組みで最初にやらかしたのが、直しきれない違反でサブエージェントが延々と再試行する事態でした。たとえばルーブリックが厳しすぎて構造的に満たせないと、ブロックと再生成が止まりません。

対策として、トランスクリプトに含まれる過去の差し戻し回数を数え、上限を超えたら block をやめて人間にエスカレーションします。

const blockCount = lines.filter(l => l.includes('"decision":"block"')).length;
if (fail.length && blockCount >= 2) {
  // 2回直しても通らなければ、止めて記録する(人間が見る)
  console.error("[grade] 再試行上限。手動レビューへ: " + fail.join(" / "));
  process.exit(0);          // ここでは block しない=ループを止める
}

採点ゲートは「自動で弾く」ことより「弾き続けない」ことのほうが運用では大事でした。止まらないゲートは、止まらないアラートと同じで、いずれ全員が無視するようになります。

運用して見えた3つの実務的な勘所

ひとつ目は、ルーブリックをコードに埋め込まず JSON ファイルに切り出したことです。基準は運用の中で必ず変わります。新しい禁止語が増えるたびにスクリプトを触っていては続きません。

ふたつ目は、採点器を決定的に保ったことです。モデルに採点させる誘惑は強いのですが、同じ提出物で合否が揺れると、サブエージェントは「運が悪かっただけ」と学習できません。機械的に弾ける違反はコードで、主観的な品質は本体プロンプトと人間で、と層を分けるのが安定しました。

みっつ目は、差し戻し理由を必ず具体的にしたことです。「品質が不十分」ではなく「H2 が 3 個(最低 4 個)」と数字で返すと、再生成が一発で通る確率が上がります。フィードバックの粒度が、自動修正ループの収束速度を直接決めます。

次の一歩として、まずは banned_wordsmin_h2 だけの最小ルーブリックで SubagentStop フックを一本通してみてください。検品ラインが一度動き出すと、基準を足していくのはあとからいくらでもできます。同じように委譲の品質に悩んでいる方の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Claude Code2026-07-18
/fork と /subtask の使い分け — 分岐は手元から切り離し、委譲はセッションに残す
Claude Code 2.1.212 で /fork は会話をバックグラウンドセッションへ複製する挙動に変わり、従来のセッション内サブエージェントは /subtask へ移りました。2つの使い分けと、分岐を増やす前に決めておきたい上限の話です。
Claude Code2026-06-13
ネスト型サブエージェントのコンテキスト予算 — 5階層委譲で品質を落とさない契約設計
サブエージェントが入れ子にできるようになった後、深い委譲ほど要約が痩せて再実行が増えました。階層間にトークン予算と受け渡しスキーマ、失敗隔離、独立グレーダーの4つの契約を入れて品質を立て直した実装記録です。
Claude Code2026-06-13
サブエージェントの入れ子は何階層が実用的か — 5階層ネスト解禁後に委譲構造を見直した記録
Claude Codeのサブエージェントが最大5階層まで入れ子にできるようになりました。実際に深い委譲を組んで「伝言ゲーム問題」による要約減衰とコスト増を確かめ、責務が3種類なら3階層で止めるという結論に至った過程と、深い委譲で守っている実務ルールを共有します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →