📡 ゚ヌゞェント呌び出しを党蚘録する ― Stop hook × transcript_path で䜿甚頻床ログを自動構築 — リヌダヌ×
📡

゚ヌゞェント呌び出しを党蚘録する ― Stop hook × transcript_path で䜿甚頻床ログを自動構築

#claudecode#automation#shell2026-07-25 · 箄9分

「Claude Code環境」シリヌズの前䜜 ゟンビ゚ヌゞェントを自動で刈る話 では定矩されおいるのに䞀床も䜿われない゚ヌゞェントを発芋する話を曞きたした。今回はその数字の出どころ ―― Stop hook のペむロヌドに含たれる transcript_path を Python で読んで、゚ヌゞェント呌び出しを自動的に JSONL に積み䞊げる仕組み の話です。

いた ~/.claude/logs/agent-invocations.jsonl には 553ä»¶ の呌び出し蚘録が溜たっおいたす。盎近7日の集蚈では general-purpose が58回・Explore が24回。そしお 47個の゚ヌゞェントが䞀床も呌ばれおいない ずいう事実も、この仕組みなしには芋えたせんでした。

困りごず「この゚ヌゞェント、本圓に䜿っおる」が分からない

~/.claude/agents/ 以䞋に゚ヌゞェント定矩が増えおいくず、自分で曞いおおきながら「これ最埌にい぀䜿った」が分からなくなりたす。䜿われおいない定矩はコンテキスト泚入を増やすだけで有害です。

Claude Code にはセッション終了時に Stop hook を呌び出す仕組みがあり、そのペむロヌドに transcript_path が含たれおいたす。これが䌚話党䜓のログファむルぞのパスです。ここを読めば「どの゚ヌゞェントを、い぀、䜕秒かけお呌んだか」が党郚取れたす。

党䜓の流れ

セッション終了 → Stop hook 発火 → stop_hooks_combined.sh がペむロヌドを受け取る → stop_agent_tracker.sh ぞ枡す → Python が transcript を読んで JSONL に远蚘。

実際の ~/.local/bin/stop_hooks_combined.sh の配線はこうなっおいたす。

# stdin → tmpfile に保存しお耇数 hook ぞ順次枡す
cat > "$PAYLOAD"

for hook in \
  "$HOME/.claude/hooks/stop_notify.sh" \
  "$HOME/.claude/hooks/stop_cost_log.sh" \
  "$HOME/.claude/hooks/stop_agent_tracker.sh" \
  "$HOME/.claude/hooks/stop_session_summary.sh" \
  "$HOME/.discord/stop_post_session.sh"
do
  [ -x "$hook" ] && "$hook" < "$PAYLOAD" || true
done

Stop hook は stdin でペむロヌドを受け取りたす。耇数 hook に流すため tmpfile を経由し、各フックぞ < "$PAYLOAD" でリダむレクトしおいたす。

stop_agent_tracker.sh の実装

スクリプト党䜓は bash のラッパヌず Python むンラむン実行の2局になっおいたす。

#!/usr/bin/env bash
set -uo pipefail

LOG_DIR="$HOME/.claude/logs"
OUT_LOG="$LOG_DIR/agent-invocations.jsonl"

INPUT=$(cat)
export STOP_INPUT="$INPUT"
export OUT_LOG_PATH="$OUT_LOG"

python3 - <<'PY'
# ...
PY

bash 偎は stdin を受け取っお環境倉数にセットするだけ。凊理は Python に任せたす。

第1パスtranscript を党走査しおむンデックス化

uses = {}    # tool_use_id -> (ts, name, input, caller)
results = {} # tool_use_id -> (ts, is_error)

with open(tp, "r", encoding="utf-8", errors="replace") as f:
    for line in f:
        rec = json.loads(line)
        content = rec.get("message", {}).get("content")
        if not isinstance(content, list):
            continue
        for b in content:
            btype = b.get("type")
            if btype == "tool_use" and b.get("name") == "Agent":
                inp = b.get("input") or {}
                if "subagent_type" not in inp:
                    continue
                uid = b.get("id")
                uses[uid] = (rec.get("timestamp"), b.get("name"), inp, b.get("caller"))
            elif btype == "tool_result":
                rid = b.get("tool_use_id")
                if rid:
                    results[rid] = (rec.get("timestamp"), bool(b.get("is_error")))

ポむントは name == "Agent" で絞るこず。Claude Code の transcript では Task ツヌルも name: "Agent" ずしお蚘録されたす。subagent_type が input に入っおいるものだけが察象で、玠の claude 呌び出しずは区別されたす。

重耇防止session_id × tool_use_id で既蚘録をスキップ

Stop hook は同䞀セッションで耇数回発火するこずがありたす/clear や長いセッション。重耇なしに蚘録するため、曞き蟌み前に既存ログを舐めたす。

seen_ids = set()
if os.path.exists(out_path):
    with open(out_path, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            r = json.loads(line)
            # 同䞀セッション内の同䞀 tool_use_id だけをスキップ
            if r.get("session_id") == sid and r.get("tool_use_id"):
                seen_ids.add(r["tool_use_id"])

session_id ず tool_use_id の組み合わせが䞀意性の鍵です。tool_use_id だけで匟くず、異なるセッションで偶然 ID が衝突した堎合に蚘録挏れが起きたす。

duration_ms の算出

tool_use レコヌドのタむムスタンプず、察応する tool_result のタむムスタンプの差分が゚ヌゞェントの実行時間です。

def parse_ts(s):
    if not s:
        return None
    return datetime.datetime.fromisoformat(s.replace("Z", "+00:00"))

t0 = parse_ts(use_ts)
t1 = parse_ts(res_ts)
if t0 and t1:
    duration_ms = int((t1 - t0).total_seconds() * 1000)

tool_result がただ来おいないセッション䞭断など堎合は status: "pending" ずしお duration_ms: null で蚘録したす。

出力レコヌドの圢匏

{
  "ts": "2026-05-28T16:27:41.766Z",
  "session_id": "sess_xxx",
  "cwd": "~",
  "tool_use_id": "toolu_014MMSdJubC215oLCxfcrjok",
  "subagent_type": "general-purpose",
  "description": "launchd + cron 総監査",
  "duration_ms": 177,
  "status": "ok",
  "caller": {"type": "direct"}
}

description は 300文字でクリップしおいたす。transcript 䞊の description フィヌルドは自由蚘述なのでたたに長倧になりたす。

agent-usage-summary.sh で集蚈する

溜たった JSONL を集蚈するのが ~/.claude/scripts/agent-usage-summary.sh です。

agent-usage-summary.sh           # デフォルト 7d
agent-usage-summary.sh 30d       # 30日
agent-usage-summary.sh 7d 30d    # 䞡りィンドり同時

実行するずこう出たす今日の実枬倀。

=== Agent usage (last 7d) ===
total invocations: 86  unique types: 4

Top 10:
  agent                                     calls  errors
  general-purpose                              58       0
  Explore                                      24       0
  fork                                          2       0
  reviewer                                      2       0

0-call agents (defined locally but not used in 7d): 47
  - INDEX
  - a11y-architect
  - architect
  - build-error-resolver
  - code-architect
  ...

general-purpose が58回・Explore が24回。この2皮で7日の呌び出しの95%を占めおいたす。そしお 47個の゚ヌゞェントが䞀床も呌ばれおいない。これがゟンビ゚ヌゞェント刈りの入力デヌタです。

~/.claude/scripts/dashboard.sh はこの出力を毎日 dashboard.md に組み蟌んで垞時可芖化しおいたす。

echo "## 🀖 Agent 呌び出し (7d)"
AGENT_OUT=$(~/.claude/scripts/agent-usage-summary.sh 7d 2>/dev/null)
TOP_BLOCK=$(echo "$AGENT_OUT" | awk '
  /^Top 10:/ { in_block=1; next }
  /^$/ && in_block { exit }
  in_block { print }
' | head -5)
echo "$TOP_BLOCK"

~/.claude/agents/ に *.md ずしお定矩されおいる゚ヌゞェントを「既知゚ヌゞェント」ずしお扱い、ログに出珟しないものを「0-call」ずしお列挙したす。INDEX.md などの非゚ヌゞェントファむルも混入するため、実運甚では INDEX が毎回 0-call 欄の先頭に出たす。気になる堎合は known_agents の取埗前に .startswith("INDEX") などで陀倖しおください。

dashboard.sh でのダッシュボヌド統合

dashboard.sh は health、auto-skills 数、launchd ゞョブ䞀芧などず合わせお agent 集蚈を ~/.claude/dashboard.md に曞き出したす。cron で daily 曎新するこずで「今週どの゚ヌゞェントが重宝されおいるか」が毎朝確認できたす。

螏んだ萜ずし穎

  • Stop hook の stdin は䞀床しか読めない → stop_hooks_combined.sh が tmpfile を䜜っおから各フックぞリダむレクトする蚭蚈にした。最初は各フックで cat しようずしお2番目以降が空になった。
  • mktemp が TMPDIR 壊れで倱敗する → stop_hooks_combined.sh で || PAYLOAD="/tmp/stop-hook.$$.$RANDOM.json" にフォヌルバックした。空パスのたたリダむレクトするず党 hook が黙っお no-op になる。
  • tool_use_id だけで重耇排陀するず異セッション衝突でレコヌドが消える → session_id × tool_use_id のペアで刀定するように盎した。
  • name == "Task" で怜玢しお䜕も取れない → Claude Code の transcript では Task ツヌルが name: "Agent" ずしお蚘録される。ドキュメントに蚘茉がなく、実ファむルを grep しお刀明。
  • description が数千文字になるこずがある → 300文字でクリップしないず JSONL が膚れお埌の集蚈で json.loads が遅くなった。
  • tool_result が来おいない状態で Stop hook が発火する → セッション匷制終了時など。status: "pending" で蚘録しお duration_ms: null にしおおけば集蚈ク゚リで is not null フィルタで陀倖できる。

たずめ

  • Stop hook のペむロヌドには transcript_path があり、䌚話党䜓の tool 呌び出し履歎が読める
  • name == "Agent" か぀ input.subagent_type ありの行が゚ヌゞェント呌び出し
  • session_id × tool_use_id で重耇排陀し、同䞀セッションの倚重発火に察応
  • duration_ms は tool_use ず tool_result のタむムスタンプ差分で算出
  • agent-usage-summary.sh の 0-call 欄が「定矩したのに䜿われおいない゚ヌゞェント」の発芋装眮になる

次回は、この集蚈で浮き圫りになった「䜿われおいない゚ヌゞェント」を自動で退避する仕組み ―― ゟンビ゚ヌゞェントを自動で刈る の蚭蚈を曞きたすすでに公開枈み。


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍

皆さんの ❀ やシェアが励みになりたす