0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

transcript.jsonlを直接パースして「何が呼ばれているか」を可視化する

0
Last updated at Posted at 2026-07-22

前作「Agent呼び出しをファイルに記録してSlackに流す」でツール呼び出しの記録について書きました。今回はその延長で、記録済みのtranscript.jsonlを直接パースして「どのSkillが何回・どのAgentが何回・どのMCPサーバーが何回呼ばれたか」を集計するスクリプトの話です。

Claude Codeの /usage はコスト($)と残量を出しますが、「今週どのSkillを一番使ったか」「どのAgentが重いか」は見えません。transcript.jsonlには全ての tool_use ブロックが残っているので、それを直接読めば /usage が教えてくれないレイヤーの使用状況を出せます。

困りごと:/usage はコストしか出さない

/usage が出すのは5hブロック・7日ブロックの消費率と、セッション単位のトークン数です。「このAutoTriggerのSkillが実は週20回呼ばれていた」「general-purpose Agentが全Agentの半数を占めている」といった品目別の内訳は出ません。

コスト最適化や環境の健全性チェックをするには、「何にトークンが使われているか」の品目が必要です。transcript.jsonlはその情報源として最適で、~/.claude/projects/ 以下に全セッション分が蓄積されています。

transcript.jsonl の構造

各ファイルは1行1レコードのJSONLです。Claude Codeが送受信した各ターンが1レコードになります。

{
  "type": "assistant",
  "uuid": "...",
  "timestamp": "...",
  "sessionId": "...",
  "message": {
    "model": "claude-sonnet-4-6",
    "role": "assistant",
    "content": [
      {
        "type": "tool_use",
        "id": "toolu_01M5Rw...",
        "name": "Read",
        "input": { "file_path": "~/..." },
        "caller": { "type": "direct" }
      }
    ],
    "usage": { "input_tokens": 12043, "output_tokens": 421 }
  }
}

集計に必要なのは message.content[] の type == "tool_use" ブロックだけです。name フィールドがツール名、input に引数が入っています。

ツール 集計のキー 抽出先
Skill input.skill スキル名(plugin:name 形式でnamespace分離も)
Agent input.subagent_type サブエージェント種別
mcp__* name の第2セグメント MCPサーバー名(mcp__<server>__<tool>)

スクリプトの核心

~/.claude/scripts/usage-breakdown.sh はBashのヒアドキュメントでPythonを埋め込んでいます。Bashからディレクトリとウィンドウ期間を受け取り、Pythonがファイルをフルスキャンします。

#!/usr/bin/env bash
ARG="${1:-7d}"
TR_DIR="$HOME/.claude/projects/$(echo "$HOME" | tr '/' '-')"
[ -d "$TR_DIR" ] || { echo "(no transcript dir)"; exit 0; }

python3 - "$TR_DIR" "$ARG" <<'PY'
import sys, json, datetime, glob, collections, os
tr_dir, arg = sys.argv[1], sys.argv[2]
SHORT = arg == "--short"

ウィンドウ期間はファイルの mtime でフィルタします。--short フラグが来たらParse処理は全て同じですが出力だけ1行に絞ります。

パース本体はシンプルです:

for path in glob.glob(f"{tr_dir}/*.jsonl"):
    mtime = os.path.getmtime(path)
    if mtime < cutoff_ts: continue
    with open(path, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            rec = json.loads(line)
            msg = rec.get("message", {}) if isinstance(rec.get("message"), dict) else {}
            content = msg.get("content") if isinstance(msg, dict) else None
            if not isinstance(content, list): continue
            for block in content:
                if block.get("type") != "tool_use": continue
                name = block.get("name", "")
                inp = block.get("input") or {}
                tool_calls[name] += 1
                if name == "Skill":
                    skill_name = inp.get("skill", "?")
                    skill_calls[skill_name] += 1
                elif name == "Agent":
                    agent_calls[inp.get("subagent_type", "?")] += 1
                elif name.startswith("mcp__"):
                    parts = name.split("__")
                    if len(parts) >= 2:
                        mcp_calls[parts[1]] += 1

Skillの plugin:name 形式(例: hookify:configure)は : でsplitしてnamespace集計も別途やっています。これでどのpluginパッケージが重いか、個別スキル名とは別軸で見られます。

実際に走らせてみる

直近7日で走らせると、こう出ます。

$ usage-breakdown.sh 7d
=== usage breakdown (last 7d, 51 transcripts) ===

total tool_use: 4230

--- top tools ---
   2583  Bash
    567  Read
    402  Edit
    151  Write
    103  mcp__claude-in-chrome__computer
     73  mcp__claude-in-chrome__javascript_tool
     62  TaskUpdate
     50  ToolSearch
     42  mcp__claude-in-chrome__navigate
     36  TaskCreate

--- top skills (2 unique) ---
      1  harness-audit
      1  superpowers:brainstorming

--- top agents (5 unique) ---
     20  general-purpose
      7  Explore
      4  ?
      2  reviewer
      1  fork

--- top MCP servers (4 unique) ---
    261  claude-in-chrome
      3  claude_ai_Google_Calendar
      3  computer-use
      2  claude_ai_Gmail

--- top plugin namespaces (1 unique) ---
      1  superpowers

この週は51セッション・4,230 tool_useで、Bashが全体の61%(2,583回)を占めていました。MCPは claude-in-chrome が群を抜いていて261回。Agentは general-purpose が最多で20回、Exploreが7回でした。

Skill呼び出しが合計2回しかないのは、この週はSkillをほとんど手動で呼ばずにAutoTrigger(CLAUDE.mdのキーワード一致)で動く設定が多かったためです。AutoTriggerはトランスクリプトには Skill tool_useとして現れるはずなので、少なさは正直に出ています。

subagent_type が "?" になっているAgentが4回あります。これは Agent ツールに subagent_type を指定しなかった(デフォルトのgeneral-purpose相当)呼び出しで、スクリプトが inp.get("subagent_type", "?") でフォールバックしている分です。実運用では "?" が増えてきたら「どのSessionか」を絞り込むと、サブエージェント指定漏れの発見に使えます。

--short モードでステータスラインに埋める

--short で1行サマリが出ます。

$ usage-breakdown.sh --short
4235 tool_use across 51 sessions (7d)

Claude Codeのステータスラインのカスタムスクリプトに組み込んでいます。コスト枠(前作の token-budget-advisor.sh 出力)と並べると「使用量と残量が一目で見える」状態になります。

# statusline hook の一例
BUDGET=$(~/.claude/scripts/token-budget-advisor.sh --short)
USAGE=$(~/.claude/scripts/usage-breakdown.sh --short)
echo "💰 $BUDGET | 🔧 $USAGE"

踏んだ落とし穴

  • mtime フィルタはファイル単位なので精度が荒い — セッションが日をまたぐと、古いターンも「最近のファイル」として集計に入ります。厳密にやるなら rec.get("timestamp") で行レベルフィルタが必要ですが、傾向把握レベルなら mtime で十分でした。
  • サブディレクトリのJSONLを拾わない — glob.glob(f"{tr_dir}/*.jsonl") はフラットな1階層しか見ません。サブエージェントのtranscriptは <session-uuid>/subagents/agent-*.jsonl に入るため、現行実装では集計外です。**/*.jsonl に変えると拾えますが、重いので意図的に外しています。
  • mcp__claude-in-chrome__computer のような長いMCPツール名 — split("__") の第2セグメントを取る実装なので、parts[1] だけが残りサーバー名は正しく抽出できます。ただしサーバー名自体に __ が入るケースでは誤抽出します(今のところ遭遇なし)。
  • 30d ウィンドウは数分かかる — 890ファイル超をフルスキャンするため、7d または --short を日常使いにして、深掘り時だけ 30d にするのが現実的です。

まとめ

  • transcript.jsonl の message.content[].type == "tool_use" だけを拾えば、Skill・Agent・MCPサーバーの呼び出し回数が取れる
  • Pythonのカウンタで most_common(10) を出すだけで「重い品目」がすぐ分かる
  • mtime 期間フィルタとPythonのヒアドキュメント埋め込みでBashスクリプト1本に収まる
  • --short で1行サマリにしてステータスラインに常時表示すると、環境の健全性をコストとは別軸で監視できる

/usage でコストを見て、このスクリプトで品目の内訳を見る。2つを合わせると「何のために何トークン使ったか」の解像度が上がります。


Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています

皆さんの ❤️ やシェアが励みになります!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?