← Cookbook トップへ

Claude Code の Token 消費をプロジェクト別に Recorder に蓄積する (Stop hook)

個人のエージェント・サブスクリプションで開発を進めると、各開発でどれだけトークンを消費しているかは意外と見えにくいもの。Claude Code の Stop hook を使い、開発(プロジェクト)ごとの Token 消費量を Recorder で見える化する手順です。

このレシピで実現できること

個人のエージェント・サブスクリプションで開発を進めるとき、それぞれの開発でどの程度のトークンを消費しているかは、意外と把握しづらいものです。Claude Code のセッション transcript はローカルにしか残らず、Anthropic Console でも API key 単位の合計しか見えません。

このレシピでは Claude Code の Stop hook を使い、開発(起動 dir =プロジェクト)ごとの Token 消費量を Recorder に蓄積して見える化します。session ごとに次の 4 つを任意の <MyPrefix> 配下に記録します。

  • <MyPrefix>/Input — 入力 token (素の prompt)
  • <MyPrefix>/Output — 出力 token
  • <MyPrefix>/CacheRead — prompt cache から再利用された token
  • <MyPrefix>/CacheCreate — prompt cache に書き込んだ token

<MyPrefix>Claude Code を起動する dir 1 つに対して 1 つ命名 します。System/Code/<app>/Claude/Tokens の形に揃えておくと、DORA レシピ で蓄積する Lines や Deploy 回数と同じツリーに並び、Recorder 上で「LOC 増加 vs Token 消費」「Deploy 回数 vs Token 消費」を 1 画面に重ねられます。これは Anthropic Console (API key 単位の Usage) では出せない切り口です。

注: Input / Output / CacheRead / CacheCreate の 4 値は、Claude API レスポンスの usage オブジェクト (input_tokens / output_tokens / cache_read_input_tokens / cache_creation_input_tokens) をそのまま転記したものです。各フィールドの正確な定義 — とりわけ input_tokens が「最後の cache breakpoint より後ろの、cache に乗らなかった入力」だけを指すこと — はオリジナルの Anthropic Prompt caching ドキュメント を参照してください。サンプル出力で Input が小さく出るのはこの定義によるものです。

前提

  • Claude Code がローカルにインストール済み
  • Recorder のアカウント
  • jq (集計に使用)

なぜ hook で送るのか

Claude Code のセッション transcript は ローカルにしか存在しません (~/.claude/projects/<encoded-cwd>/<session-id>.jsonl)。各行に message.usage = {input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens} がタイムスタンプ付きで残っているので、ローカルから直接読んで Recorder に流すのが自然です。GitHub Actions の cron では到達できないデータ源です。

セットアップ手順

1. Recorder の PAT を発行

Settings → API Tokens で発行 → 文字列をその場でコピー → ~/.zshrc 等で export RECORDER_PAT="..." しておきます。

2. 集計対象の dir を事前確認する

Claude Code は launch cwd を /- で置換して project dir を作ります。集計したい dir で 1 回 Claude Code を起動すると、~/.claude/projects/ の下に対応する dir が作られます。ls して basename を控えます:

ls ~/.claude/projects/
# 例: -Users-me-code-myapps   ← この行を控える

3. Stop hook script を配置し、対象 dir と metric prefix を反映する

~/bin/post_claude_tokens.sh を作成。case の 2 行だけ自分の値に書き換えれば、残りはそのままで動きます。

#!/usr/bin/env bash
set -euo pipefail
 
: "${RECORDER_PAT:?env RECORDER_PAT is not set}"
 
PAYLOAD=$(cat)
# PAYLOAD は生の制御文字を含み厳密な JSON でない場合があるため、jq に通さず
# transcript_path (値はパスなので " を含まない) だけ文字列抽出する
TRANSCRIPT=$(printf '%s' "$PAYLOAD" | grep -o '"transcript_path":"[^"]*"' | head -1 | sed 's/.*:"//; s/"$//')
PROJECT_BASENAME=$(basename "$(dirname "$TRANSCRIPT")")
 
# ─── ⬇ ここの 2 行だけ自分の環境に書き換える ⬇ ───
case "$PROJECT_BASENAME" in
  -Users-me-code-myapps)                                # ← 手順 2 で確認した dir basename
    METRIC_PREFIX="System/Code/myapps/Claude/Tokens" ;; # Recorder 上で使う prefix
  *)
    exit 0 ;;
esac
# ─── ⬆ ここまで ⬆ ───
 
read -r INPUT OUTPUT CREAD CCREATE <<<"$(jq -R -s -r '
  [ split("\n")[]
    | select(length > 0)
    | (fromjson? // empty)            # 制御文字混じりで壊れた行はスキップ
    | .message.usage // empty
  ]
  | reduce .[] as $u (
      {input:0, output:0, cread:0, ccreate:0};
      .input    += ($u.input_tokens // 0)
      | .output   += ($u.output_tokens // 0)
      | .cread    += ($u.cache_read_input_tokens // 0)
      | .ccreate  += ($u.cache_creation_input_tokens // 0)
    )
  | "\(.input) \(.output) \(.cread) \(.ccreate)"
' "$TRANSCRIPT")"
 
post() {
  local name=$1 value=$2
  curl -sS -X POST https://api.recorder.nasebanal.com/api/v1/records \
    -H "Authorization: Bearer $RECORDER_PAT" \
    -H "Content-Type: application/json" \
    -d "{\"name\":\"$name\",\"unit\":\"tokens\",\"data_type\":\"integer\",\"chart_type\":\"line\",\"value\":$value}"
}
 
post "$METRIC_PREFIX/Input"       "$INPUT"
post "$METRIC_PREFIX/Output"      "$OUTPUT"
post "$METRIC_PREFIX/CacheRead"   "$CREAD"
post "$METRIC_PREFIX/CacheCreate" "$CCREATE"

実行権限を付けます:

chmod +x ~/bin/post_claude_tokens.sh

集計対象 dir を複数にしたい場合は case 文に arm を追加するだけです。

4. Claude Code の settings に Stop hook を登録

~/.claude/settings.json:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/bin/post_claude_tokens.sh"
          }
        ]
      }
    ]
  }
}

5. 動作確認

手順 2 で控えた dir で 1 session 動かして終了させ、Recorder の「タグ一覧」に <MyPrefix>/{Input,Output,CacheRead,CacheCreate} が現れていれば成功です。

サンプル出力

数 session 動かすと、4 つの token 種別が Recorder 上で 1 つのツリーに並びます。System/Code/nasebanal/Claude/Tokens の実データを、桁の違う 2 つの軸に分けて見るとこうなります。

まず素の Input / Output (cache を経由しない token)。Output (緑) が数十万 token 規模まで伸びる一方、Input (赤) はほぼベースラインに張り付いています。入力の大半は prompt cache 側に回るため、生の入力 token は出力よりむしろ小さく出ます。

Claude Code の Input / Output token 時系列チャート (Recorder)

次に cache 側。CacheRead (緑) は数千万 token 規模まで達し、CacheCreate (赤) は相対的に小さく収まります。スケールが I/O 側の約 100 倍違うので別チャートにしています。コンテキストの大半が prompt cache から再利用されている = 実際に課金対象となる素の入力 token が小さく抑えられている、ということが読み取れます。

Claude Code の CacheCreate / CacheRead token 時系列チャート (Recorder)

これは Anthropic Console の API key 単位 Usage では出せない、dir / アプリ別の内訳です。

⚠️ シークレットの取扱い

  • RECORDER_PAT~/.zshrc 等で export するだけにして、script に直書きしないでください
  • 漏洩が疑われたら Recorder Settings → API Tokens で失効 → 新規発行 → 環境変数を上書きしてください
  • Stop hook は すべての Claude Code session で発火します。case にない project は *) exit 0 でサイレントに弾く設計なので、未追跡の作業内容が Recorder に紛れ込むことはありません