はじめに
31万回のAPI呼び出し、請求額21万円。
わずか4時間半の出来事でした。
これは、Vibe Codingを実践する中で実際に起きた事故の記録です。
最近、Claude CodeやCursorなどを使ったAI主導の開発スタイル、いわゆるVibe Codingに取り組む人が増えてきていると思います。自分もその一人で、「AIにどこまで任せられるのか」を日々試しています。
その中で、割と痛い目を見たので共有します。同じような開発スタイルを実践している方、これから試してみようと思っている方の参考になれば幸いです。
背景
Claude Codeを使って、自分では一切コードを書かないスタイルでPoCアプリを開発していました。
PoCの機能の中に、地図上に2地点間のルートを描画するというものがありました。もともとは直線で結ぶだけのシンプルな表示でしたが、Google Maps Routes APIを使って実際の道路に沿ったルートを描画するように変更しました。もちろん、この実装もClaude Codeにやらせています。
何が起きたか
タイムライン
その日の流れはこうです。
11:00頃 — 実装が完了し、動作確認を実施。画面上ではルートが表示されているのを確認して、「動いてるな」と思い、ブラウザのページを開いたまま放置しました。他の作業に移ったんですね。
15:30頃 — GCPから請求アラートのメールが届きます。「設定した予算の50%を超過しました」という内容。
嫌な予感がして、すぐにGCPのコンソールを開いてログを確認しました。
Routes APIの呼び出し回数を見て、血の気が引きました。
31万回。
即座にブラウザのページを閉じましたが、時すでに遅し。請求額は約21万円。普段の利用料のおよそ50倍です。
その後、GCPのサポートに事情を説明して減額交渉を行い、最終的には約10万円まで下げていただきました。それでも十分に痛い金額ですが。
技術的な原因分析
では、なぜ31万回もAPIが呼び出されたのか。
ログとコードを調査して分かったのは、Claude Codeが「既存のコードを壊さないように」という配慮をした結果、新旧2つのコンポーネントが並存する実装になっていたということです。
もう少し具体的に説明します。
もともと直線でルートを描画していた旧コンポーネントと、Routes APIを使う新コンポーネントが両方存在していて、条件分岐が不完全なまま両方が同時に描画される状態でした。
観測された挙動としては以下の通りです。
- デフォルトでは旧コンポーネント(直線描画)が表示される
- Routes APIが実行されると、一瞬だけ新コンポーネントが表示される
- すぐにまた旧コンポーネントの表示に戻る
ここから推測される無限ループの構造はこうです。
新コンポーネントがマウントされる
→ Routes APIが呼び出される
→ レスポンスを受けて状態が更新される
→ 状態更新により再レンダリングが走る
→ 新コンポーネントが再マウントされる
→ またRoutes APIが呼び出される
→ ...(以下無限ループ)
ReactのuseEffect内でAPIを呼び出し、そのレスポンスでstateを更新し、そのstate変更がコンポーネントの再マウントを引き起こし、再びuseEffectが発火する——という典型的な無限ループパターンです。
ただし厄介なのは、このループが目に見えにくい形で発生していた点です。
なぜ気づけなかったのか
これが一番反省すべきポイントです。
まず、画面上は正常に見えていました。デフォルトでは旧コンポーネントが表示されており、地図もルートも問題なく描画されている。新コンポーネントが表示されるのは「一瞬」だけなので、目視ではほぼ気づけません。
動作確認したときも、「ルートが表示されている、OK」と判断してしまいました。
そして何より、AI生成コードへの過信がありました。
Claude Codeは非常に優秀で、普段はかなり質の高いコードを生成してくれます。だからこそ、「動いているなら大丈夫だろう」という思い込みが生まれていました。ブラウザの開発者ツールでネットワークタブを開いていれば、大量のAPIリクエストが飛んでいることに一瞬で気づけたはずです。
でも、やらなかった。信頼しすぎていたんですね。
学びと教訓
この事故から得た学びを整理します。
① 生成コードのレビューは省略できない
Vibe Codingは「自分でコードを書かない」スタイルですが、コードを読まなくていいわけではないです。
特に以下のような箇所は重点的に確認すべきでした。
- 外部API呼び出しを含むコード:課金に直結するので、呼び出し頻度やループの有無は必ず確認する
- レンダリングロジック:useEffectの依存配列、stateの更新タイミングなど
- AIが「既存を壊さない」判断をした箇所:新旧コードが並存している場合、意図しない副作用がないか
AIが気を利かせてくれた部分こそ、注意して見る必要があります。
② 防御的実装を徹底する
外部APIを呼び出す処理には、必ず安全装置をつけるべきです。
- レートリミットの設定:一定時間内の呼び出し回数に上限を設ける
- リトライ上限の設定:失敗時の再試行回数を制限する
- デバウンス/スロットリング:短時間での連続呼び出しを抑制する
これらはAIに実装を指示する際に、明示的にプロンプトに含めるべきです。AIは言われなければこうした防御的な実装を省略することがあります。
③ ガードレールを設計する
今回、GCPの請求アラートを設定していたおかげで、4時間半で気づくことができました。これがなければ、もっと被害が拡大していた可能性があります。
実践すべきガードレールとしては以下が挙げられます。
- クラウドの請求アラート:段階的な閾値で設定する(例:予算の25%、50%、75%、100%)
- APIキーの使用量制限:Google Cloud Consoleで1日あたりのリクエスト上限を設定できる
- 開発環境のクォータ設定:本番とは別に、開発環境では厳しめの制限をかける
特にAPIキーの使用量制限は、今回のようなケースでは直接的な防御になります。設定していれば上限に達した時点でリクエストが弾かれ、31万回も呼ばれることはなかったはずです。
④ AIの「配慮」が生むリスクを認識する
これは構造的な問題として認識しておくべきだと思います。
Claude Codeは「既存のコードを壊さないように」という配慮から、旧コンポーネントを残したまま新コンポーネントを追加しました。人間のエンジニアでも同様の判断をすることはありますが、AIの場合はその配慮が適切かどうかを自分で検証しないという点が異なります。
AIは「既存を壊さない」ことを優先した結果、不完全な条件分岐を残しました。これは一見「丁寧な実装」に見えますが、実際にはバグの温床になっていました。
AIの判断を鵜呑みにせず、「なぜこういう実装にしたのか」を確認する癖をつける必要があります。
おわりに
Vibe Codingは開発の生産性を大きく向上させてくれる素晴らしいアプローチだと思っています。この事故を経験した今でも、自分はAI主導の開発を続けています。
ただ、AIが生成するコードは「たぶん正しい」のであって「必ず正しい」わけではない。特に課金が発生する外部サービスとの連携部分では、人間が責任を持って確認する必要がある。当たり前のことですが、痛い目を見て改めて実感しました。
21万円(最終的には10万円)は、なかなか高い授業料でした。
でも、この経験のおかげで今はガードレールの設計を最初に考えるようになりました。
この記事が、同じような事故を防ぐための参考になれば嬉しいです。