定額のサブスク(Claude のMaxプランやProプラン)を契約しているのに、claude -p(Claude Code の非対話モード)を自動化スクリプトから呼んでいたら、裏で API の従量課金を消費していた——という事故を踏みました。

しかも気づいたのは、API残高がゼロになって定時ジョブが全滅したときです。それまでは何のエラーも出ず、静かに課金が溶けていました。

原因はシンプルで、.env に置いていた ANTHROPIC_API_KEY が悪さをしていました。同じ構成で自動化している人は、けっこう踏みうる罠だと思うので、再現条件と直し方を残しておきます。

何が起きたか

子プロセスの認証経路を確認する。引き継ぐ環境を確認:認証関連の変数名を確認、値や秘密情報をログに出さない。用途ごとに選ぶ:API利用かログイン利用かを決める、不要な認証変数を子プロセスへ渡さない。実際の経路を確認:認証状態と利用明細で確認、CLIのフラグだけで請求先を決めつけない
サブスクリプションにも利用上限がある。無制限とは限らない。

ある朝、定時で回しているジョブ(記事生成やリサーチをまとめて通知に流す類のもの)が、軒並み失敗していました。ログを見ると、こう出ています。


Credit balance is too low

最初、意味が分かりませんでした。私が使っているのは Claude の定額サブスクプランです。当時は月額契約で動いていると思っていましたが、サブスクリプションにも利用上限があります。「クレジット残高」なんて概念は、サブスクには無いはずなんです。

「クレジット残高が低い」と言われるのは、今回の環境ではAPI キーによる従量課金(pay-as-you-go)の残高不足を示していました。つまり私のジョブは、サブスク認証ではなく、API キー経由の従量課金で claude -p を叩いていたということでした。

そして恐ろしいのは、残高があった数日間は、一切エラーを出さずに従量課金を消費し続けていたこと。残高がゼロになって初めて、Credit balance is too low で全部が止まり、ようやく異常に気づいたのです。

⚠️ 注意: サブスク契約だから安心、ではありません。claude -p は条件次第でサブスクを無視して従量課金経路を選びます。そして残高がある間はエラーを出さないため、課金が発生していること自体に気づけません。

原因:ANTHROPIC_API_KEY がサブスクより優先される

Claude Code の認証には、ざっくり2つの経路があります。

1. サブスク認証:claude に一度ログイン(claude.ai アカウント)した状態。MaxやProの定額枠で動く

2. API キー認証:環境変数 ANTHROPIC_API_KEY を使い、ConsoleのAPI利用として課金される。ANTHROPIC_AUTH_TOKEN は別に扱う必要があり、gateway/proxyのBearer認証など接続先・契約によって請求先が変わる。名前だけでConsoleの従量課金とは判定できない

問題は優先順位です。環境変数に ANTHROPIC_API_KEY があると、Claude Code はそちらを優先して API キー認証で動きます。 サブスクにログイン済みでも、環境変数があれば従量課金経路に倒れます。

公式のAPIキー管理案内は、subscription利用時にANTHROPIC_API_KEYを未設定にし、/statusで認証方法を確認するよう説明しています(2026年10月9日確認)。管理者向け設定の一文だけから、すべての認証・課金経路を推定するのは避けます。

なぜ自動化ジョブだけが踏んだのか

対話的にターミナルで claude を使う分には、私の環境では ANTHROPIC_API_KEY をシェルに export していなかったので、ずっとサブスクで動いていました。

事故ったのは自動化ジョブの中だけです。理由はこれ。

  • アプリ側のコードが、別用途のために .env を load_dotenv() で読み込んでいた
  • その .env には ANTHROPIC_API_KEY が入っていた(別の従量課金前提のモジュールが正当に使っている)
  • load_dotenv() で os.environ に ANTHROPIC_API_KEY が乗る
  • claude -p を subprocess で起動するとき、env={**os.environ} でまるごと子プロセスに継承していた

結果、子プロセスの claude -p が ANTHROPIC_API_KEY を見つけ、サブスクを無視して従量課金で動いていた、というわけです。


# 事故っていたコード(イメージ)
import os
from dotenv import load_dotenv

load_dotenv()  # .env の ANTHROPIC_API_KEY が os.environ に乗る

subprocess.run(
    ["claude", "-p", prompt],
    env={**os.environ, "TERM": "xterm-256color"},  # ← API キーごと継承してしまう
)

env={**os.environ} は、子プロセスに環境を渡す定番の書き方です。だからこそ気づきにくい。.env に API キーが1個あるだけで、意図せず従量課金に倒れます。

直し方:子プロセスの env から API キー系だけ外す

いちばん手堅いのは、claude -p を起動する瞬間だけ、ANTHROPIC_API_KEY と ANTHROPIC_AUTH_TOKEN を子プロセスの env から取り除くことです。不要なAPI認証の継承を避けられます。ログイン状態、apiKeyHelper、gatewayやクラウドプロバイダーの設定も別に確認し、同じ子プロセス環境でclaude auth statusと/statusを確認してください。


import os
import subprocess


def _subscription_env() -> dict:
    """claude -p をサブスク枠で回すための env。
    ANTHROPIC_API_KEY があると CLI が従量課金経路を優先するため除去する。"""
    env = {
        k: v
        for k, v in os.environ.items()
        if k not in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN")
    }
    env["TERM"] = os.environ.get("TERM", "xterm-256color")
    env["NO_COLOR"] = "1"
    return env


subprocess.run(["claude", "-p", prompt], env=_subscription_env())

ポイントは、os.environ 自体はいじらないことです。.env の ANTHROPIC_API_KEY を従量課金モードで正当に使っている別モジュールがある場合、それは壊したくありません。だから「親プロセスの環境はそのまま」「claude -p に渡す子プロセスの env からだけ外す」という形にします。親プロセスの変数は変わりませんが、子プロセスが必要とするgateway認証まで除去しないよう、用途を確認します。

別の選択肢

状況によっては、こちらの方が早いこともあります。

  • そもそも export しない:claude -p を呼ぶ前のシェルで unset ANTHROPIC_API_KEY する。ただし .env を読むアプリ内からだと効きにくい
  • forceLoginMethod で縛る:claude 側の設定で API キー認証を起動時にブロックする。ログイン方式を制限する管理設定です。認証供給元による拒否の条件は公式仕様を確認してください。ただし API キーで動かしたい他用途まで巻き込むので、用途が混在する環境では注意
  • キーの置き場を分ける:従量課金で使いたいモジュール専用の名前(例:別の環境変数名で受けてコード内で ANTHROPIC_API_KEY に橋渡し)にして、グローバルな ANTHROPIC_API_KEY を環境に置かない

私は、従量課金で正当にキーを使うモジュールが同居していたので、「子プロセス env から外す」案を採りました。

いま課金経路がどっちか、を確認する方法

「自分のジョブはサブスクで動いている」と思い込むのが一番危ないので、確認しておくと安心です。

手軽なのは、claude -p を呼んでいるのと同じ環境変数の状態で、対話的に起動して状態を見ることです。


# 子プロセスに渡しているのと同じ env でログイン状態を確認
claude
# 起動後に /status を見ると、認証方法を確認できる

あるいは、API キーが環境にあるかをまず疑います。


python3 -c 'import os; print("\n".join(k for k in os.environ if k.startswith("ANTHROPIC")))'

ここに ANTHROPIC_API_KEY が出てくるなら、その状態で claude -p を叩いている限り、ConsoleのAPI認証を優先する可能性があります。変数の存在だけで判断を終えず、実際の認証状態と利用明細を確認してください。値は出力せず、共有ログへ認証情報を残さないようにします。

📝 メモ: API残高がある間はエラーが出ません。「動いているから正常」ではなく、「どの認証で動いているか」を一度は確認しておくのがおすすめです。

教訓:env={**os.environ} は便利だけど、認証情報まで素通しする

今回の根っこは、Claude Code 特有の話というより、subprocess に env={**os.environ} でまるごと環境を渡すという、ありふれた書き方にありました。

.env に何か1個、認証系の環境変数が増えるたびに、それを起動する全ての子プロセスが影響を受けます。普段は無害でも、今回のように「その環境変数を見て挙動を変えるツール」を呼んだ瞬間、意図しない経路に倒れます。

外部コマンドを subprocess で叩くときは、

  • 本当にその子プロセスに必要な環境変数だけを渡す
  • 少なくとも、認証系(APIキー・トークン)は意識して取捨選択する

これを癖にしておくと、今回みたいな「静かに課金が溶ける」系の事故を防ぎやすくなります。

最後に念のため。.env や API キーはログ・コミットに残さないよう、いつもどおり気をつけてください。今回のように「env を子プロセスへ素通し」していると、デバッグで env をダンプした拍子にキーが流出する、という二次災害も起こり得ます。

サブスクで Claude Code を回しているつもりが、いつの間にか従量課金で溶けていた——同じ構成の人は、一度 python3 -c 'import os; print("\n".join(k for k in os.environ if k.startswith("ANTHROPIC")))' で変数名だけを確認し、実際の認証状態も照合してください。