FLINTERS BLOG
🧐

Claude CodeのInput Tokenが増える原因を、利用ログから調べてみた

に公開1

はじめに

こんにちは。FLINTERSでエンジニアをしている佐藤です。

Claude Codeを使っていると、入力した文章は短いにもかかわらず、Input Tokenだけが大量に消費されていることがありました。

当初は、自分が入力しているプロンプトが長すぎるのだと思い、文章を短くしたり、言い回しを削ったりしていました。

しかし、実際に利用ログを集計してみると、主な原因は別のところにありました。

この記事では、私の利用ログをもとに、次の内容を紹介します。

  • Input Tokenの内訳
  • 長いセッションで消費量が増えた理由
  • 実際に効果があった対策
  • 自分のログを集計する方法

本記事に記載している数値は、筆者個人の利用ログを匿名化し、一部を丸めたものです。環境や使い方によって結果は異なるため、絶対値ではなく傾向としてご覧ください。

結論

私のログでは、Input系トークンの内訳は次のとおりでした。

  • cache_read_input_tokens:94.5%
  • cache_creation_input_tokens:5.3%
  • input_tokens:0.2%

新しく入力した内容よりも、既存のコンテキストを再利用するトークンが圧倒的に多い状態でした。

Claude CodeがClaudeへ渡すInputには、ユーザーがその場で入力した文章だけでなく、次のような情報も含まれます。

  • これまでのユーザーの指示
  • Claudeが返した過去の回答
  • 読み込んだファイルの内容
  • コマンドの実行結果
  • CLAUDE.mdなどの共通指示
  • ツールの定義
  • サブエージェントから返された情報

これらのうち、キャッシュ済みの情報が再利用された場合は、
cache_read_input_tokensとして記録されます。

そのため、画面上では数行の質問を送っただけに見えても、
回答を生成する際には同じセッション内に蓄積された大量の情報がInputとして扱われることがあります。

そのため、プロンプトの文章を少し短くするだけでは、Input Tokenの削減効果は限定的です。
セッションの区切り方や、読み込ませる情報量を見直す必要があります。

約450ターン続けた巨大セッション

特にトークン消費が多かったセッションを確認すると、約450回の応答を同じセッションで続けていました。

最初は1つの調査から始めたものの、そのまま次のような作業まで継続していました。

  • 原因調査
  • コード修正
  • テスト
  • ログ調査
  • コードレビュー

すべて同じリポジトリ内の作業だったため、同じコンテキストを持った方がスムーズだろうと安易に考えて同じセッションを使用していました。

しかし、1応答あたりのInput系トークンを確認すると、セッションの序盤と終盤では大きな差がありました。

フェーズ 1応答あたりのInput系トークン

  • 序盤 約83,000
  • 中盤 約360,000
  • 終盤 約673,000

終盤では、序盤の約8倍になっています。

セッションの後半では、大きくなったコンテキストを抱えたまま、細かい確認や修正を何度も繰り返していることがわかりました。

その結果、この1セッションだけで、集計対象全体の半分以上のInput系トークンを占めていました。

なぜコンテキストが膨らんだのか?
特に影響が大きかった原因を次に挙げます。

1. セッションを目的ごとに区切っていなかった

「同じリポジトリの作業」という理由で、目的の異なる作業(調査、実装、テスト)も同じセッションで続けていました。

しかし、前の調査で読み込んだ大量のログやファイルが、次の作業でも必要とは限りません。

例えば、次の2つは同じリポジトリ内の作業ですが、必要なコンテキストは大きく異なります。

  • ログインエラーの原因を調査して修正する
  • 別機能のPull Requestをレビューする

この経験から、セッションはリポジトリ単位ではなく、目的単位で区切るほうがよいと分かりました。

2. 大きな出力をそのまま読み込ませていた

テスト項目などExcelファイルを出力を、そのまま読み込ませていました。
一度読み込ませた情報は、その後のやり取りでもコンテキストとして参照される可能性があります。

ファイル全体を読ませる前に、検索条件やカラムを指定するだけでもセッションへ持ち込む情報量を減らせます。

3. CLAUDE.mdに情報を入れすぎていた

以前は、常に必要とは限らない説明・作業指示までCLAUDE.mdへ記載していました。
一方で、特定機能の仕様や長い作業手順は、必要なときだけ参照する別ファイルへ分けました。

CLAUDE.mdは、詳しければ詳しいほどよいわけではなく、毎回読み込まれるため必要情報に絞ることがInput Token削減には有効です。

実際に効果があった対策

ここからは、私の環境で特に効果があった対策を紹介します。

1. タスクが終わったら/clearする

最も効果が分かりやすかった方法です。
現在は、次のような単位でセッションを分けています。

  • エラー調査・そのまま小規模修正。
  • 実装のみ。
  • 1つのPull Requestをレビューする。

反対に、次のような広い単位ではセッションを継続しないよう意識しています。

  • 今日の作業すべて。
  • このリポジトリの作業すべて。

話題や目的が変わった場合は、同じリポジトリ内の作業であっても/clearしています。

2. 続きが必要な場合は/compactする

もし実装途中で、設計上の判断や未完了の作業を残したい場合は、/clearではなく/compactを使います。

圧縮前に、残してほしい情報を指定してあげると無駄がないです。

次の作業を続けられるように、以下だけ残してください。
- 確定した仕様
- 変更済みファイル
- 未完了タスク
- 実行済みテスト
- 重要な設計判断
調査途中の仮説や、解決済みのログは省略してください。

残す情報を明示することで、作業の継続に必要な内容を保ちながら、不要になった経緯やログを減らしやすくなります。

3. コマンドの出力を最初から絞る

Claudeへ「必要な箇所だけ確認して」と依頼しても、実行するコマンド自体が大量の結果を返せば、その情報がコンテキストへ入る可能性があります。

そのため、依頼文だけでなく、コマンドの段階でも出力を絞ります。

#全ログではなく末尾だけ確認する
tail -n 100 application.log

# エラー周辺だけ確認する
grep -n -C 5 "ERROR" application.log

# 検索結果の先頭だけ確認する
find src -type f | head -n 50

# ファイルの一部分だけ確認する
sed -n '120,220p' src/services/user.ts

最初は必要最小限の範囲だけを読み込み、不足している場合に追加の範囲を確認する(させる)ようにしています。

4. 大きなログやファイルをそのまま読み込ませない

コマンド出力だけでなく、ファイルを確認する場合も、最初から全文を読み込ませないようにします。
「念のため全体を確認する」という依頼をした場合、対象によってはコンテキストを急激に増やす原因になります。
特に私は「網羅的に確認して」という指示をよく投げていたので、そこも良くなかったと反省しています。
現在は絞れるポイントは絞るように意識しています。

5. 広い調査はサブエージェントへ分ける

大量のファイルを横断して確認する作業や、複数のログを比較するような調査は、メインのセッションですべて処理せず、サブエージェントへ分けるようにしました。

例)
メインセッション:調査
サブエージェント:調査から派生した実装

モデルも作業内容に応じて使い分けています。
複雑な設計判断や実装方針の検討には高性能なモデルを使い、単純なファイル探索やログの分類には比較的低コストなSonnetやHaikuを利用しています。

負荷のかかりやすい調査をメインのセッションで続けると、読み込んだファイルや検索結果が会話コンテキストに蓄積し、その後のやり取りでもInput Tokenが増えやすくなります。

自分のログを集計する

Claude CodeのJSONLログにUsage情報が記録されていれば、次のPythonスクリプトでログを集計できます。

import json
from collections import defaultdict
from pathlib import Path
totals: defaultdict[str, int] = defaultdict(int)
for path in Path.home().glob(".claude/**/*.jsonl"):
    with path.open(encoding="utf-8") as file:
        for line in file:
            try:
                record = json.loads(line)
                usage = record.get("message", {}).get("usage")
            except (json.JSONDecodeError, AttributeError):
                continue
            if not usage:
                continue
            totals["input"] += usage.get("input_tokens", 0)
            totals["cache_write"] += usage.get(
                "cache_creation_input_tokens",
                0,
            )
            totals["cache_read"] += usage.get(
                "cache_read_input_tokens",
                0,
            )
            totals["output"] += usage.get("output_tokens", 0)
input_total = (
    totals["input"]
    + totals["cache_write"]
    + totals["cache_read"]
)
if input_total:
    print(
        "cache_read:",
        f"{totals['cache_read'] / input_total * 100:.1f}%",
    )
    print(
        "cache_write:",
        f"{totals['cache_write'] / input_total * 100:.1f}%",
    )
    print(
        "input:",
        f"{totals['input'] / input_total * 100:.1f}%",
    )

実行例

  work git:(main)  python3 usage-script.py
cache_read: 94.3%
cache_write: 5.5%
input: 0.3%

まとめ

利用ログを実測すると、Input系トークンの大半はcache_read_input_tokensであり、長期間続けたセッションが消費量を大きく押し上げていました。

Claude Codeでは、プロンプトを数文字削ることよりも、不要になったコンテキストを持ち越さないことのほうが、Input Tokenの削減に寄与しました。

Input Tokenの消費量が気になる場合は、プロンプトの長さではなく、セッションの長さと、読み込ませている情報量を確認してみるとよさそうです。

FLINTERS BLOG
FLINTERS BLOG

Discussion

nanaperika5460nanaperika5460

キャッシュは10分の1のコストになるので、そこまで必死にclear、コンパクトしなくても良いと思ってる派です。
クリアし過ぎると無駄に本来不要なキャッシュ書き込みが走って、余計にコストがかかる場合があると思っています。
ただ、目的単位で区切るというのは同意見です。

1