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?

Claude Codeのトークン消費を削る実践 — 計測してから削った5箇所

0
Posted at

はじめに:コスト上限ガードが一度も動いていなかった

当社(合同会社ジョインクラス、社員は僕1人)は、launchd の定時ジョブから claude -p を無人で叩いて、部門ごとの状況整理・記事ネタ補充・リード収集などを回しています。

月の上限は $200、$160 で Slack に警告を出す仕組みも作っていました。安心していたのですが、2026年9月25日に中身を確かめたら このガードは一度も作動しない状態 でした。

原因は単純で、コスト記録の関数が「呼び出し側が渡した概算値」を足すだけの作りだったからです。朝ダイジェストのスクリプトは毎回固定で 0.02 を渡していました。架空の数字で上限判定をしていたわけです。

この記事では、まず 実測できるようにした方法 を書き、そのあと 実測を見ながら削った5箇所 を、実際に動いているコードと一緒に紹介します。

前提:概算をやめて total_cost_usd を記録する

claude --output-format json で実行すると、応答の JSON に total_cost_usd と usage が入っています。これをそのまま積めば実測になります。

当社の実装は2ファイルです。まずラッパーです。

.company/scripts/lib/claude-run.sh
#!/bin/bash
# claude を無人実行するときの共通ラッパー。実コストを記録する。
# 使い方:
#   source .company/scripts/lib/claude-run.sh
#   RESULT=$(claude_run dept-dev --effort low -p "…")
#
# 標準出力には応答テキストだけを出すので、従来の `claude -p` と差し替えられる。
# 注意: --bare は認証を読まないため "Not logged in" で失敗する。使わない。

_CLAUDE_RUN_LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

claude_run() {
  local label="$1"; shift
  claude --output-format json \
         --fallback-model claude-sonnet-5 \
         --max-budget-usd 3 \
         "$@" 2>/dev/null \
    | python3 "$_CLAUDE_RUN_LIB/record-cost.py" "$label"
}

ポイントは 標準出力に応答テキストだけを流す ことです。既存の RESULT=$(claude -p "...") を RESULT=$(claude_run ラベル -p "...") に置き換えるだけで済むので、十数本あるスクリプトを一気に移行できました。

次に、JSON を受け取って記録する側です(要点だけ抜粋)。

.company/scripts/lib/record-cost.py
def main():
    label = sys.argv[1]
    raw = sys.stdin.read()
    try:
        d = json.loads(raw)
    except json.JSONDecodeError:
        sys.stdout.write(raw)          # JSONでなければ素通しする
        return 0

    sys.stdout.write(d.get("result") or "")

    path = pathlib.Path(os.environ.get("COST_FILE") or DEFAULT_COST_FILE)
    month = datetime.date.today().strftime("%Y-%m")
    try:
        t = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        t = {}

    if t.get("month") != month:         # 月が替わったら前月分を退避して開き直す
        if t.get("month"):
            path.with_name(f"cost-tracker-{t['month']}.json").write_text(
                json.dumps(t, ensure_ascii=False, indent=1), encoding="utf-8")
        t = {"month": month}

    cost = float(d.get("total_cost_usd") or 0)
    usage = d.get("usage") or {}

    before = float(t.get("total_usd") or 0)
    t["total_usd"] = round(before + cost, 6)
    jobs = t.setdefault("jobs", {})
    j = jobs.setdefault(label, {"calls": 0, "usd": 0.0, "errors": 0, "cache_creation_tokens": 0})
    j["calls"] += 1
    j["usd"] = round(j["usd"] + cost, 6)
    j["cache_creation_tokens"] = j.get("cache_creation_tokens", 0) + int(
        usage.get("cache_creation_input_tokens") or 0)
    if d.get("is_error"):
        j["errors"] = j.get("errors", 0) + 1

    path.write_text(json.dumps(t, ensure_ascii=False, indent=1), encoding="utf-8")

    # 閾値は跨いだ瞬間だけ通知する(毎回鳴らすと無視されるようになる)
    now = t["total_usd"]
    if before < STOP_USD <= now:
        notify_slack(f":rotating_light: 月間AI支出が ${now:.2f} に到達")
    elif before < WARN_USD <= now:
        notify_slack(f":warning: 月間AI支出 ${now:.2f} / ${STOP_USD:.0f}")

ジョブ単位で cache_creation_tokens まで残しているのが効きました。実際に貯まったデータはこんな形です(2026年11月10日時点の一部)。

cost-tracker.json
{
 "month": "2026-11",
 "total_usd": 30.3496,
 "jobs": {
  "dept-dev":     { "calls": 10, "usd": 3.505745, "cache_creation_tokens": 362369 },
  "sme-concerns": { "calls": 10, "usd": 6.30988,  "cache_creation_tokens": 620803 },
  "replenish":    { "calls": 25, "usd": 12.151766, "cache_creation_tokens": 1041967 }
 }
}

落とし穴

  • 2>/dev/null を外してデバッグする: 認証切れなどは stderr に出ます。移行直後は外して1回手で叩いてください。
  • --bare は使わない: 起動を軽くしたくて試しましたが、認証情報を読まず Not logged in で失敗しました。
  • JSON でない出力は素通しする: パースに失敗しても応答を捨てないようにしておかないと、後続の処理が空文字で動いてしまいます。

計測してわかったこと:1回あたりの固定費が大きい

計測を始めてまず驚いたのは、「2+2は?」と聞くだけで1回 $0.27〜0.47 かかっていた ことです。

内訳を見ると、キャッシュ作成が約41,000トークン。そのうち自社のファイル(CLAUDE.md・エージェント定義・スキル)は約5,800トークンしかありません。残りは Claude Code 側の固定オーバーヘッドです。

つまり「CLAUDE.md を短くすればいい」という話の効果は小さくて、効くのは呼び出しの回数と、1回の中で何をさせるか でした。以下の5箇所はこの前提で削っています。

削った5箇所

1. 定型ジョブは --effort low にする

部門ごとの状況整理のような定型の仕事に深い推論はいりません。

auto-dept-dev.sh
source "$SCRIPT_DIR/lib/claude-run.sh"
RESULT=$(claude_run dept-dev --effort low -p "
...(STATE.md を読んで差分を要約する指示)
")

当社では --effort low を付けていないジョブが1本だけ残っていて、比べると差が出ています。

ジョブ effort 1回あたり
dept-dev low 約 $0.35
dept-sales low 約 $0.32
sme-concerns 指定なし 約 $0.63

プロンプトの中身が違うので純粋な比較ではありません。ただ、キャッシュ作成トークンも約1.7倍あり、ツール呼び出しが増えて往復が多くなっている形跡があります。検証方法は簡単で、同じジョブを --effort low あり・なしで数回ずつ回し、jobs.<ラベル>.usd / calls を比べてください。

2. --allowedTools で工程ごとに道具を絞る

記事ネタ補充(replenish)は一番呼び出しが多いジョブです。調査工程と執筆工程で、渡すツールを分けています。

auto-replenish-articles.sh
# 調査工程:読むだけ
claude_run replenish --effort low --allowedTools "Read,WebSearch,WebFetch" -p "..."

# 執筆工程:ネット検索は不要
claude_run replenish --effort low --allowedTools "Write,Read,Edit" -p "..."

執筆中に「念のため検索」が始まると、検索結果がそのままコンテキストに乗ってトークンが膨らみます。道具を渡さなければ寄り道できません。セキュリティ面でも、無人実行で余計な権限を持たせないのは良いことです。

3. --max-budget-usd 3 で1回の暴走を止める

ラッパーに入れている --max-budget-usd 3 は、1回の実行で使える上限です。無人ジョブで怖いのは平均ではなく、ループにはまった1回 です。月の上限ガードだけだと、気づいたときには1日分が溶けています。

値は普段の1回あたり($0.3〜0.6)の5倍以上にしてあります。きつくしすぎると、正常な処理が途中で打ち切られて記事が半分だけ書かれる、といった別の事故が起きるからです。

4. 実行前に月の上限をチェックする

記録だけでは止まりません。各スクリプトの先頭で、実測値を見て止めます。

common.sh
check_cost_limit() {
  local tracker="$COMPANY_DIR/scripts/cost-tracker.json"
  [ -f "$tracker" ] || return 0

  local total=$(jq '.total_usd' "$tracker")
  if (( $(echo "$total >= 200" | bc -l) )); then
    log_error "月間コスト上限到達: \$${total}。実行を中止。"
    return 1
  fi
}

# 各ジョブの冒頭
check_cost_limit || exit 0

関数自体は前からありました。変わったのは 読んでいる数字が本物になったこと だけです。最初に書いた「ガードが動いていなかった」問題は、ここで初めて解消しています。

5. 呼び出し回数そのものを減らす

固定費が1回 約41,000トークンなら、10回を1回にまとめるのが一番効きます。当社では次の順で見直しました。

  1. jobs を usd の大きい順に並べる
  2. 上位のジョブについて「毎回呼ぶ必要があるか」「前回から入力が変わっていないのに呼んでいないか」を見る
  3. 変わっていなければ claude を呼ばずに終わる

上位を出すコマンドはこれだけです。

jq -r '.jobs | to_entries | sort_by(-.value.usd)[]
  | "\(.value.usd | tostring | .[0:6])\t\(.value.calls)回\t\(.key)"' \
  .company/scripts/cost-tracker.json

当社の11月は replenish(25回・約$12)が全体の4割を占めていました。次に手を入れるのはここです。

結果

月ごとの実測は次のとおりです。

月 実測合計 備考
2026-09 $26.41 9/25 から計測開始(約6日分)
2026-10 $128.96 1か月まるごと計測
2026-11 $30.35 10日時点

正直に書くと、9月は計測期間が短く、「削る前」のきれいな比較はできていません。それでも10月の $128.96 に対して、11月は10日時点で $30.35(このペースなら月 $90 前後)です。そして何より、$160 警告・$200 停止のガードが本物の数字で動くようになった ことが一番の成果でした。

まとめ:今日やること

  1. claude -p を --output-format json に変えて、total_cost_usd をジョブ別に記録する
  2. 1週間貯めてから、usd の大きいジョブを見る
  3. 定型ジョブは --effort low、工程ごとに --allowedTools を絞る
  4. --max-budget-usd で1回分、check_cost_limit で月分の上限をかける
  5. 最後に、呼び出し回数そのものを減らす

順番が大事です。計測より先に CLAUDE.md を削っても、当社の場合は全体の1割強にしか効きませんでした。

経営の目線で言うと、AIのコストで怖いのは高いことではなく、わからないこと です。数字が見えれば判断できます。僕はこの作業で「コストは10分の1になるが、売上が10倍になるわけではない」ことも改めて実感しました。浮いたお金と時間は、売上に直接つながる仕事に回しています。

参考書籍

CLAUDE.md・設定・コスト管理の設計を体系的にまとめた本を書いています。

  • 『CLAUDE.md設計パターン』
  • launchd / cron での無人実行まわりは『Claude Code 全自動化バイブル』

書籍一覧: https://zenn.dev/joinclass?tab=books

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?