Claude Code のトークン消費を可視化する — cost.log と毎朝の Discord レポートでAIコストを確認する
Claude Code を本気で使うとコストが見えなくなる
Claude Code を毎日叩いていると、使用量制限に到達することがある。
これは「使いすぎ」でもあるが、「使った量がリアルタイムに見えない」ことが原因だ。
API のレスポンスには毎回コストが含まれているのに、その情報はそのまま捨てられる。
集計しなければ、昨日どのくらい使ったかも、どのコマンドが重いかも分からない。
分からなければ、節約のしようも改善のしようもない。
この記事では、cost.log にトークン消費を蓄積し、毎朝 Discord にレポートを流す仕組みを示す。
SRE が自分の AI 開発費を運用対象として扱う、その入口の話だ。
なぜ可視化しないと判断材料が増えないのか
Claude Code のコストは、3つの理由で「気づいたら膨らんでいる」状態になりやすい。
第一に、コストは使った後にしか分からない。
cronで1日数百回叩けば、トークン消費は大幅に上がる。
事後にしか見えないなら、事前のチューニングは推測でしかできない。
第二に、リポジトリ別・コマンド別の比較ができない。
調査用のスクリプトと本番パイプラインを同じ財布で動かしていると、どちらが重いかは集計しないと見えない。
重いコマンドから削れば、品質を落とさずにトークン消費を半分にできる余地は十分ある。
第三に、1日の積算がリアルタイムで見えないと「今週もうやばい」を体感できない。
やりたいことを詰め込んだ結果、5時間制限や週上限に到達することがある。
どの処理でトークン消費割合が高いかを確認する。
これは AWS 運用と同じ構造だ。
コストを SLO のように扱うなら、計測・通知・サーキットブレーカーの3点セットが要る。
これを踏まえて、Claude Code側の実装を4層で組み立てる。
実装の全体像
層は4つに分ける。L1は計測・実行を担うインフラ層を定義する。
| 層 | ファイル | 責務 |
|---|---|---|
| L1-1: 計測 | ~/.claude/scripts/lib.sh |
各 claude --print の結果からusageを抽出して cost.log に追記 |
| L1-2: 集計 | ~/.claude/scripts/cost-report.py |
cost.log を日次/月次/リポ別/コマンド別で集計 |
| L1-3: 統合 | <repo>/scripts/morning-report.sh |
集計結果を毎朝のパイプラインレポートに埋め込む |
| L1-4: 通知 |
notify_discord 関数 |
Discord webhookにembeds形式で投げる |
L1はトイル領域なので、Claude Code自身に書かせる。
ここから1層ずつ、要点だけ追う。
L1-1: cost.log に何を残すか
cost.log はTSVで1行 = 1リクエスト。
run_cmd 関数(claude --print のラッパー)が呼ばれるたびに1行追加する。
date time repo command usage_usd duration_ms input_tokens output_tokens exit_code
2026-04-29 03:14:21 repo-a research 0.0382 18421 12450 3120 0
2026-04-29 03:14:43 repo-b check 0.1264 41250 45120 8140 0
usage_usdは人間が判断しやすいよう、金額換算した際の数値だ。
カラム設計のポイントは3つ。
-
repoを残す: マルチリポジトリ運用なら必須。後でリポ別集計が打てる -
commandを残す:researchdraftなどコマンド単位の単価が見える -
exit_codeを残す: 失敗にもコストが乗る。エラー率と単価を一緒に追える
抽出はAPIレスポンスJSONから取る。
claude --print --output-format json は total_cost_usd と usage を返してくれる。
# lib.sh 抜粋(簡略化)
json_output=$(claude --print --output-format json \
--allowed-tools "$allowed_tools" \
--disallowed-tools "$disallowed_tools" \
-- "$prompt")
usage_usd=$(echo "$json_output" | node -e "
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
console.log(d.total_cost_usd || 0);
")
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$(date +%Y-%m-%d)" "$(date +%H:%M:%S)" \
"$repo_name" "$cmd_name" \
"$usage_usd" "$duration_ms" "$input_tokens" "$output_tokens" "$exit_code" \
>> "$COST_LOG"
入力トークンは input_tokens + cache_creation_input_tokens + cache_read_input_tokens を合算する。
キャッシュヒット率を別で見たければカラムを増やせばいいが、料金観点では合算で十分判断できる。
ログ出力を全 claude --print 呼び出しに集約すると、抜け漏れがなくなる。
個別スクリプトに claude --print を直書きせず、必ず run_cmd 経由に揃えるのが肝になる。
L1-2: 集計スクリプトの設計
cost-report.py は cost.log を読んで集計するだけの単機能ツール。
判断は何もしない。L1の責務に閉じる。
$ python3 cost-report.py
=== 月次レポート: 2026-04-01 〜 2026-04-29 ===
合計: $12.4831(487回, エラー2件)
トークン: 入力1,245,120 / 出力318,420
--- リポジトリ別 ---
repo-a: $9.8421(320回, 入力945,120 / 出力248,140)
repo-b: $2.6410(167回, 入力300,000 / 出力70,280)
--- コマンド別(コスト順) ---
repo-a/search: $2.8420(45回)
repo-b/review: $1.5180(28回)
--- 日別推移 ---
2026-04-01: $0.4120(18回)
2026-04-02: $0.5810(22回)
設計ルールは 3 つ。
- 集計は filter → aggregate の 2 ステップにする: 期間指定とリポ別を同じ集計関数で処理できる
- エラー件数を別に出す: 失敗にも料金は乗る。エラー率が高いコマンドはチューニング候補
- JSON 出力モードを持たせる: 自動化スクリプトから呼ぶ際にgrepでなくパースしたい
予算チェックは --budget-check 引数で実装する。
$ python3 cost-report.py --budget-check 50.00
✅ BUDGET OK: $12.4831 / $50.00(残り$37.5169)
超過したら exit 1 を返すと、cronスクリプトの先頭で
python3 cost-report.py --budget-check 50.00 || exit 1 と書くだけで
「月予算を超えたら今夜のジョブを止める」が実装できる。
サーキットブレーカーをスクリプト1行で書ける形にしておくと、緊急時に発動できる。
L1-3: 毎朝のレポートに埋め込む
集計だけでは見ない。
毎朝決まった時間にDiscordに流して、コーヒーを淹れる前に目に入る場所に置く。
# morning-report.sh 抜粋
COST_SCRIPT="${HOME}/.claude/scripts/cost-report.py"
if [ -f "$COST_SCRIPT" ]; then
YESTERDAY_COST=$(python3 "$COST_SCRIPT" --date "$YESTERDAY" 2>/dev/null \
| sed 's/^/- /' || echo "- データなし")
MONTHLY_COST=$(python3 "$COST_SCRIPT" 2>/dev/null \
| sed 's/^/- /' || echo "- データなし")
COST_SECTION="### 昨日
${YESTERDAY_COST}
### 今月
${MONTHLY_COST}"
fi
# レポート本文に埋め込む
cat > "$REPORT_FILE" <<EOF
# パイプライン実行レポート: ${DATE}
## 結果サマリ
- ステータス: ${STATUS}
- エラー件数: ${ERROR_COUNT}
- 完了ステップ数: ${DONE_COUNT}
## 使用量
${COST_SECTION}
## 公開待ち記事
- Zenn: ${QUEUE_ZEN} 本
EOF
cron 設定は単純に毎朝8時。
0 8 * * * /home/me/repo/scripts/morning-report.sh >> /home/me/repo/scripts/cron.log 2>&1
レポートを見るタイミングを朝1回に固定すると、気になって何度も覗きに行くのがなくなる。
コストを毎時間チェックするのは人間がやるべき仕事ではない。
L1-4: Discord に投げる
通知層は notify_discord 関数1つに集約する。
webhook URL は ~/.claude/config/discord.env に置いて全リポジトリで共有する。
# lib.sh
notify_discord() {
local webhook_url="$1"
local payload="$2"
if [ -z "$webhook_url" ]; then
return 0
fi
curl -s -X POST "$webhook_url" \
-H "Content-Type: application/json" \
-d "$payload" \
> /dev/null 2>&1 || true
}
embeds形式で色を分けると、緑(正常)/ オレンジ(警告)/ 赤(エスカレーション)が一目で分かる。
# morning-report.sh 抜粋
if [ "${STATUS}" = "ESCALATION" ]; then COLOR=16711680 # 赤
elif [ "${STATUS}" = "ERROR" ]; then COLOR=16753920 # オレンジ
elif [ "${STATUS}" = "NO_RUN" ]; then COLOR=8421504 # グレー
else COLOR=5763719 # 緑
fi
DISCORD_BODY=$(python3 -c "
import json
payload = {
'embeds': [{
'title': '${NOTIFY_TITLE}',
'description': '''${COST_SECTION}''',
'color': ${COLOR},
'footer': {'text': '${DATE}'}
}]
}
print(json.dumps(payload))
")
notify_discord "${DISCORD_WEBHOOK_CONTENT}" "$DISCORD_BODY"
設計の肝は「失敗を握り潰さない」こと。
notify_discord 内部の || true は、Discord障害でメインジョブを止めないため。
ただし通知失敗自体はcron.logに残るので、後追いで気づける状態にしておく。
想定される落とし穴
仕組みを設計するときに気をつけたい点が3つある。
-
キャッシュヒットを別管理しないと変動を読み違えやすい:
input_tokensにcache_read_input_tokensを合算していると、料金は安いのにトークン数が多く見える。請求と乖離が出るようなら別カラムを切る選択肢もある - 失敗コストが意外に効きやすい: パイプラインが途中で死んでもトークンは消費されている。エラー率の高いコマンドはチューニング候補にしたい
-
月初リセットの錯覚:
--date指定なしは月初〜今日の累計。日次で見たいなら--date $(date +%Y-%m-%d)を明示する
数値が出ていれば、こうした罠も翌朝のレポートで拾える。
拾えればチューニングに回せる。
取れていないと、翌月の請求でしか異常に気づけない。
まとめ — 自動化はコスト管理の入口、判断は人間が握る
cost.logに蓄積し、cost-report.pyで集計し、morning-report.shで毎朝レポートし、Discordで目に入る場所に置く。
これだけで、Claude Codeの出費はSLOのように扱える対象になる。
L1(計測・集計・通知)は Claude Code自身に書かせて構わない。
判断する領域だけは人間が握る。
- 何を SLO にするか: 月予算 / 日予算 / コマンド単価のどれを基準にするか
- どう超過対応するか: ジョブを止めるか、警告だけかける運用にするか
- どこを削るか: コスト上位コマンドの1回分を必要工数として認めるか、削るか
このうち「数値を見ながらの調整」はAIが補助できるが、ビジネス判断としての境目は人間にしか引けない。
仕組みを作るのは Claude Code、運用するのはSRE。
分業の軸が決まると、AI開発費の追跡は通常のSRE業務の延長として扱える。
Discussion