UPSIDER Tech Blog

AI支援ツールがメモリを食い潰す? Claude Codeが起こした「Vitest暴走事件」の顛末と再発防止策

こんにちは! エンジニアのmitsuiです。

皆さんのチームでは、AIによるコーディング支援ツール、活用していますか? 私たちのチームでも、「Claude Code」(クロード・コード:日々のコーディングをAIがサポートしてくれる頼れる相棒)を導入し、その賢さに日々感動しています。「この機能のテストコード書いて」とお願いすれば、サッと雛形を生成してくれる。まさに生産性のブースターです。

しかし先日、その「賢さ」と「よしなにやってくれる」便利さが、思わぬ落とし穴となって私たちに襲いかかりました。気づけば開発マシンはメモリを食い潰されてファンが唸りを上げ、ローカルでは通るはずのテストがCIでだけ失敗する…。

この記事では、そんな「AI支援ツールが賢すぎて逆に困った」という、未来的な失敗談と、そこから得た学びを共有します。AIと上手く付き合っていきたい全てのエンジニアに、共感いただける話かもしれません。

背景:始まりは Claude Code への軽いお願いだった

コトの起こりは、ある新機能の実装中に、私がClaude Codeにこう頼んだことでした。

「今書いた関数のテスト、よろしく!」

Claude Codeは即座に素晴らしいテストコードを生成し、さらには気を利かせてテスト実行までしてくれようとします。ターミナルには vitest の実行ログが流れ、「うん、テストも通ったな、完璧!」と私は満足していました。

しかし、その数時間後。どうもMacBookの動作が重い。ファンが「フォーン!」と唸りを上げ、VS Codeの入力もおぼつかない。アクティビティモニタを見ると、やはりいました。メモリを数GBも食い散らかす謎の node プロセスが。

さらに追い打ちをかけるように、他のメンバーから「CIでテストがコケてるんだけど、何か心当たりある?」との連絡が。私のローカルでは何度やっても成功するのに、です。

一見、無関係に見える2つの問題。しかし調査を進めると、その根源は同じ、Claude Codeが「よしなに」実行してくれた、あのテストコマンドにあったのです。Claude Codeは、私たちのプロジェクト作法を考慮せず、最も一般的と思われる方法( npx vitest )でコマンドを実行してしまったのでした。

TL;DR:AIアシスタントに仕事を任せるための「お作法」

忙しい方のために結論から。AIが引き起こした怪奇現象の顛末と、私たちが立てた対策はこうです。

  • 現象1(メモリ暴走): Claude Codeが実行した vitest コマンドが、デフォルトの watch モードを起動。裏でプロセスが常駐しメモリを圧迫していた。
  • 現象2(CIでの失敗): Claude Codeが npx でコマンドを実行したため、pnpm で管理された依存関係とズレが発生。「ローカルOK、CI NG」の原因となっていた。
  • 対策: AIが「どのコマンドを叩くべきか」で迷わないよう、プロジェクトの"お作法"を package.json の scripts に集約
    1. "test": "vitest --run" を定義し、誰が(AIが)実行してもワンショットで終わるようにした。
    2. preinstall フックで only-allow pnpm を設定し、npx 等による意図しないコマンド実行を物理的にガードした。

AIに正しく動いてもらうためには、AIが迷わないような「道しるべ」を人間が作ってあげる必要があった、というわけです。

詳細:AIの「親切心」が招いた混乱の舞台裏

なぜこんなことになったのか、もう少し詳しく見ていきましょう。

Claude Codeは気を利かせた。ただ、起動オプションまでは知らなかった

Claude Codeは、私たちがVitestを使っていることを理解し、テストを実行するために vitest コマンドを選択してくれました。これはAIとして100点満点の挙動です。

問題は、Vitest側の仕様にありました。vitest はデフォルトで watch モード、つまりファイルの変更を監視し続けるモードで起動します。

出典: Vitest CLI Guide

AIはただ「テストを実行せよ」という指示に忠実に従っただけ。しかし、その結果としてバックグラウンドでプロセスが生き続け、私のマシンのリソースを静かに蝕んでいたのです。

AIの「よしなに実行」を仕組みで制御する

CIでテストがコケた問題も、根本はClaude Codeのコマンド実行方法にありました。Claude Codeは、パッケージを実行する最も一般的な方法として、おそらく npx vitest というコマンドを内部で組み立てて実行しました。

しかし、私たちのプロジェクトは pnpm を使って依存関係を厳密に管理しています。便利な npx ですが、時には pnpm-lock.yaml で定義されたバージョンとは異なるキャッシュを参照してしまい、ローカル環境とCI環境の間に微妙な差を生み出すことがあります。また、CI上は例えば src配下のコード全てが対象、となっていても Claude Code上では特定のファイルでだけ実行し、それがpushされていた、となります。これが「ローカルOK、CI NG」の正体でした。

これらの問題を一挙に解決するのが、プロジェクトとしての公式なコマンド実行方法を一つに定めることでした。それが package.jsonscripts です。

{ 
  "scripts": { 
    "preinstall": "npx only-allow pnpm",
    "test": "vitest --run" 
    // ... other scripts 
   } 
}

scripts.test に "vitest --run" を定義することで、watchモードを防ぎます。

さらに scripts.preinstall に "npx only-allow pnpm" を設定。これにより、pnpm install 以外の方法(npm installなど)で依存関係がインストールされるのを防ぎ、npxが予期せぬ挙動をするリスクを低減させます。

この設定により、「このプロジェクトでテストを実行する際の正解は pnpm test である」と定義できます。人間だろうがAIだろうが、このコマンドを叩けば意図通りに実行される。まさにAIのための、そして私たちのためのガードレールです。

学び:AIは“アシスタント”であり、すべてを読み取る“執事”ではない

今回の一件から得た最大の学びは、AIは万能ではなく、文脈を与えることで初めて力を発揮する存在だということです。

Claude Code は、確かに「最もよく使われる方法」を選んでくれました。けれどそれは、私たちのプロジェクトにとっての最適解ではありませんでした。

AIが力を発揮するには、人間側が「前提・作法・意図」を丁寧に伝えることが不可欠です。

そのための最適な手段が、コードとしてルールを明文化すること。

package.jsonscripts は、そんなチームの暗黙知を、AIが解釈できる形で表現する場所として、非常に有効なのだと実感しました。

まとめ

AIツールとの共存は、ツール任せにすれば良いという話ではありません。

むしろ、人間側がどれだけ「前提を整えてあげられるか」が、成功と失敗を分ける分岐点になります。

あなたのプロジェクトでも、AIが“よしなに”動いてくれているようで、実は迷っていないでしょうか?

環境整備は、これからのエンジニアにとって欠かせないスキルになっていくのかもしれません。