Skip to content

クライアント側「トークン予算概算機」の自爆:並行エージェントの掛け算バグが引き起こした利用枠の消失

1. 概要

LLMのAPIやSaaS型AIエージェントを利用する際、ユーザーの急激なトークン消費(請求額の高騰)を防ぐために、クライアントアプリ側で「トークン予算の概算」や「レート制限」を行うガードレールが設けられることがある。 しかし、このクライアント側の制限ロジックにバグがあり、さらにLLMの「プロンプトキャッシュ(Prompt Caching)」や「並行処理(マルチエージェント)」の仕組みと整合性が取れていない場合、実際にはリソースに余裕があるにもかかわらず、クライアントが勝手にでたらめな計算で「予算オーバー」をでっち上げ、ユーザーの利用枠を一瞬で食い潰してサービスを停止させる深刻な可用性バグが発生する。 本稿では、Claude Codeで発生した「トークン予算爆発バグ(anthropics/claude-code#37917)」を事例に、エージェントハーネスにおけるクライアント側リミッター設計の死角について分析する。


2. インシデントの分析:5分で枯渇する「5時間枠」の怪

2.1. 現象:カジュアルな利用で発生する「レートリミット到達」

Claude Codeの特定のアップデート(v2.1.81前後)以降、多くのユーザーから「数分の作業、あるいは『バージョンを確認して』と1回質問しただけで、週の利用枠の50%や、5時間分のセッション枠が一瞬で100%枯渇してロックされてしまう」という異常なトークン消費の報告が相次いだ。

❯ Operation blocked: Rate limit reached.

ユーザーは「モデルも設定も変えていないのに、トークンの計算ロジックが狂っている」と主張し、怒り狂った開発者の一部は他のエージェントツール(CodexやOpenCodeなど)へ乗り換える事態に発展した。

2.2. 原因A:APIを叩かない「仮想エラー(Synthetic Error)」の偽装

コミュニティによるログの精密なデバッグ(#40584)により、このエラーはAnthropicのAPIサーバーから返された本物のレートリミットエラーではなく、Claude Codeクライアントが内部に持つ「クライアント側レートリミッター(予測シミュレータ)」がローカルででっち上げたエラーであることが判明した。

  • 証拠ログ:
    json
    { "model": "<synthetic>", "input_tokens": 0, "output_tokens": 0 }
    APIリクエストの送信履歴を見ると、モデル名が <synthetic>(合成)となっており、実際の送信トークン数は0であった。 つまり、クライアント側が「次のリクエストは巨大だから、APIに送るまでもなく予算オーバーだ」と勝手にシミュレーションして判断し、サーバーにリクエストを1通も送ることなく通信をローカルで遮断していた。

2.3. 原因B:プロンプトキャッシュを無視した「並行エージェントの掛け算バグ」

このクライアント側シミュレータが犯した決定的な計算ミスは、「コンテキストの重複」と「プロンプトキャッシュ」の無視、および**「並行エージェント数による乗算」**であった。

現代のLLM APIは、同一セッション内の巨大な履歴(コンテキスト)を再利用する際、「プロンプトキャッシュ」を適用して料金やトークン予算を大幅に割り引く(キャッシュヒット時はコストが数分の一になる)。 しかし、クライアント側のシミュレータは以下の計算ロジックを踏んでしまった。

  1. キャッシュの無視: コンテキスト全体(例: 400Kトークン)が毎回のAPI呼び出しで「常に100%新規に消費される」前提でシミュレーションした。
  2. 並行処理の乗算: さらに、3つのサブエージェント(並行ワーカー)が同時に同じコードベースのコンテキストを参照して動く際、キャッシュを共有するのではなく、単純に 「サブエージェント数 × 全体コンテキスト(3 × 400K = 1.2Mトークン)」 として消費量を掛け算で計算した。

このでたらめな乗算により、セッションが少し長くなっただけで、クライアント内部の「架空 of 消費メーター」が一瞬で跳ね上がり、ユーザーの現実の予算や枠がまだ大量に残っているにもかかわらず、システムを完全にロックしてしまっていた。


3. 実体験:賢すぎるリミッターが引き起こした「全自動アクセス遮断」

  • 実体験エピソード: ある大規模なデータクローリングおよび解析SaaSにおいて、外部APIの料金が高騰するのを防ぐため、チームは「クライアント(クローラーのコントローラー)側で、過去のスクレイピング量から推測される月間予算をシミュレーションし、予算を超えそうになったら自動でクローリングを一時停止する」という賢い「セーフガード機能」を実装した。 しかし、ある日、一時的にエラーが多発してクローラーが「リトライ(再実行)」を繰り返した際、このシミュレータは「リトライによる重複リクエスト」をすべて「新しいユニークなデータの取得」として重複カウントしてしまい、実際のサーバー側でのデータ取得量は予算の10%未満だったにもかかわらず、シミュレータは「すでに予算120%に達した」と誤判定した。 この結果、システムは「自衛」のために全クローラーのプロセスを自動で永久ロックし、本番のデータ同期が丸一日停止する大規模な障害を引き起こした。 「APIの実態(キャッシュやリトライ)を考慮しない、クライアント側での過剰な『知ったかぶり』の予算制限」は、防御ではなく単なる自己免疫疾患(サービス拒否バグ)に化ける。

4. 解決策とクライアント側レートリミット의 設計原則

このバグから得られる、エージェントハーネス設計における教訓は以下の通りである。

4.1. 予算の評価元(Source of Truth)は常にサーバー側にする

クライアント側でのシミュレーションはあくまで「警告」や「UI上の参考値の提示」に留め、実際の実行可否の判定(Block)は、APIサーバーから返ってくるリアルタイムの残量データ(レスポンスヘッダーに含まれる残予算トークン情報など)のみに基づいて決定論的に行うべきである。

4.2. プロンプトキャッシュとアーキテクチャのセマンティクスの同期

クライアント側に制限ロジックを持たせる場合、API側の最新の割引仕様(Prompt Caching等)や、エージェントシステム自体のマルチエージェント共有メモリの構造を完璧に反映しなければならない。 「400Kのコンテキストを持つエージェントが3台動く = 1.2Mの消費」という単純な足し算・掛け算で計算するような雑なロジックは、並行処理システムにおいて確実に偽陽性のロックを引き起こす。


5. 結論

AIエージェントのコンテキストウィンドウが巨大化し、マルチエージェントによる並行処理が当たり前になる中、トークン制御はもはや単なる「文字列の長さカウント」ではない。 クライアント側で勝手にシミュレーションを行ってエラーをでっち上げる「架空のレートリミッター」は、可用性を破壊する最大の脆弱性となり得る。 インフラ設計者は、レート制限の評価をサーバー側の一次情報(Source of Truth)に委ね、クライアント側には複雑な予測ロジックを持ち込まないという、**「ガードレールロジックの極小化(Simple Client Control)」**を徹底しなければならない。


6. 参考文献 / 一次情報

Shiori pointer-RAG automation experiment.