ビルドが通らない時の切り分け順: 型・依存・設定・環境差を15分で詰める
ビルドエラーで「直して」と全文を貼る前に。型/依存/設定/環境差を疑う順番、最小再現の作り方、Claude Codeに渡して直す反復の回し方を、僕の事故込みで。
金曜の夜、デプロイ直前に npm run build が真っ赤になりました。
僕がやったのは、ログ全文をコピーして Claude Code に「直して」と投げること。返ってきたのは、それっぽい修正5ファイル。ビルドは通りました。通ったんですが——翌週、まったく同じエラーがまた出たんです。最初に壊れていた1行を、誰も見ていなかった。AIは末尾のスタックトレースを読んで、症状だけを上手に隠していました。
ビルドエラーって、たいてい「直す」より先にやることがあります。どこが最初に壊れたかを特定して、原因の種類を1つに絞ること。これが切り分けです。今日はその順番と、Claude Codeに小さく渡して直す反復のやり方を、僕の失敗込みで書きます。
エラー文そのものの読み方はエラーメッセージの読み方に、デバッグ全般の手順はgit bisectで挟み撃ちするデバッグにまとめてあります。この記事は「ビルドが通らない時の切り分け反復」だけに絞ります。
この記事の要点
- ビルドエラーは、まず最初の失敗行を保存する。末尾のスタックトレースは上流のエラーの“余波”であることが多い。
- 疑う順番を固定する: 型 → 依存・import → 設定(frontmatter等)→ 環境差(CIと手元)。当てずっぽうの順番だとログが比較できない。
- Claude Codeには「ログ全文+直して」ではなく、最初の失敗行・仮説1つ・検証コマンド1つだけ渡す。差分が小さくなる。
- 手元で通るのにCIで落ちるなら、コードを疑う前に環境差(Nodeバージョン・OS・大文字小文字・環境変数)を並べる。
- 最後に「原因・変更・証拠」の3行を残す。次に同じエラーを見た人(未来の自分)が最短で戻れる。
なぜ「直す」より「切り分け」が先なのか
ビルドが落ちると、頭の中は「早く通したい」でいっぱいになります。だからログ全文を貼って、AIに丸投げしたくなる。気持ちはすごく分かります。
でも、ビルドログには3つのものが混ざっています。最初に壊れた1行、それに引きずられて出た後続のノイズ、そしてAIが「ついでに」やりたがる無関係なリファクタ。この3つを一緒くたに渡すと、AIは一番目立つ末尾のエラーに飛びつきます。冒頭の僕の事故がまさにそれでした。
切り分けというのは、料理でいう「火を止めて、何が焦げたか確かめる」工程です。煙(末尾のログ)に向かって水をかけても、コンロの火(最初の失敗)は消えません。順番を決めて、最初の1か所を特定する。地味ですが、ここを飛ばすと必ず後で同じエラーに戻ってきます。
疑う順番を固定する: 型 → 依存 → 設定 → 環境差
切り分けで一番大事なのは、毎回同じ順番で疑うことです。順番がブレると、前回のログと今回のログが比較できません。僕はこの4段階で固定しています。
| 順番 | 疑うもの | 典型的なエラーの見え方 | 最初の一手 |
|---|---|---|---|
| 1 | 型 | Type 'X' is not assignable, Property does not exist | 型定義1か所を見る。安易に any で潰さない |
| 2 | 依存・import | Cannot find module, ERR_MODULE_NOT_FOUND | 依存追加の前にファイル存在とパスを確認 |
| 3 | 設定 | frontmatter, InvalidContentEntry, schema系 | 本文より先に設定ファイルとschemaを見る |
| 4 | 環境差 | 手元は通るのにCIだけ permission denied 等 | コードを直す前にCIと手元の差を並べる |
なぜこの順番かというと、**上にあるものほど「自分のコードの問題」で、下にいくほど「環境の問題」**だからです。型エラーはほぼ確実に自分の書いたコードの中で完結します。依存・importはコードと外部の境目。設定はプロジェクト固有のルール。環境差は一番厄介で、自分のマシンでは再現すらしないこともある。
だから上から潰します。型エラーを放置したまま「CIの問題かも」と環境を疑い始めると、迷宮に入ります。逆に、型・依存・設定が全部クリーンなのに落ちるなら、それは環境差の強いサインです。
1. 型エラー: まず「どこで型が崩れたか」を1か所に絞る
型エラーで一番やりがちなのが、エラーが出た行に as any を足して黙らせることです。僕も昔やりました。ビルドは通る。でも、型が崩れた本当の場所は上流にあって、any はそれを下流で握りつぶしているだけ。次の改修でまた爆発します。
型エラーは「どこで型が変わったか」を遡るのが正解です。Property 'foo' does not exist on type なら、その値がどこから来たか(API応答か、frontmatterか、関数の戻り値か)を1件だけ実際に出して確かめる。型エラーの直し方はエラーメッセージの読み方で、スタックトレースのどこから読むかを詳しく扱っています。
2. 依存・import: Cannot find module は依存不足とは限らない
Cannot find module を見ると反射的に npm install したくなりますが、依存不足以外にも原因がたくさんあります。拡張子なしのimport、type: module とCJSの混在、package.json の exports 設定、そして「まだ生成されていないファイルをimportしている」パターン。
だから、依存を足す前に存在確認のコマンドを1つ打ちます。
# パッケージが本当に解決できないのか確認(依存追加の前に)
node -p "require.resolve('対象パッケージ名')"
これがエラーなら依存不足。解決できるのにビルドが落ちるなら、importパスのtypoかESM/CJSの混在を疑います。package update は lockfile・CIキャッシュ・Nodeバージョンまで波及するので、typoで済む話に依存を上げると差分が無駄に大きくなります。
3. 設定ミス: 本文より先にschemaと設定を見る
Astroのようなコンテンツサイトだと、記事本文は正しいのに frontmatter のschema違反でビルドが落ちます。heroImage が抜けている、pubDate の形式が違う、lang が未定義——こういう時に記事本文を直し始めると、原因から遠ざかります。
設定系のエラーが出たら、本文ではなく site/src/content.config.ts(content collectionのschema定義)と、該当する frontmatter を先に見ます。schemaが要求しているフィールドと、実際の frontmatter を突き合わせれば、たいてい一発でわかります。
4. 環境差: 手元で通るのにCIで落ちる
一番嫌なやつです。ローカルでは緑なのに、CIだけ赤い。これはコードの問題ではなく、環境の差であることがほとんどです。Nodeのバージョン違い、OS(macOS/WindowsとLinux)の大文字小文字の扱い、環境変数の欠落、proxy、書き込み権限。
ここでコード修正を頼むのは最悪手です。再現しない環境を相手に、当てずっぽうの修正が積み上がります。やることは、コードを触る前に差を並べること。詳しくは後半の専用セクションで扱います。
まず同じ順番で証拠を取る
切り分けの前に、証拠を毎回同じ手順で取ります。順番を固定すると、前回のログと比較できて「これは前と同じ型エラーだ」と気づけます。macOS / Linux / WSL なら次の shell をそのまま使えます。
git status --short
npm run build 2>&1 | tee build.log
status=${PIPESTATUS[0]}
if [ "$status" -ne 0 ]; then
# 最初の失敗候補だけを上から20行だけ抜く(末尾の余波は見ない)
grep -Ein "error|failed|ERR_|Cannot|TypeError" build.log | head -n 20
exit "$status"
fi
npm test -- --runInBand
Windows PowerShell では、npm の解決が shell によって変わることがあります。CI と同じ挙動に寄せるため、npm.cmd を明示します。
$ErrorActionPreference = "Continue"
git status --short
npm.cmd run build *> build.log
$buildExit = $LASTEXITCODE
if ($buildExit -ne 0) {
Select-String -Path build.log -Pattern "error|failed|ERR_|Cannot|TypeError" |
Select-Object -First 20
exit $buildExit
}
npm.cmd test -- --runInBand
ポイントは、成功させることではなく最初に失敗した場所を保存することです。ログの末尾は上流の失敗に引きずられたスタックトレースになりがちで、そこだけ見ると見当違いを直します。Claude Code に渡す前に、最初の error だけを抜き出す。これだけで余計な修正がごっそり減ります。
コピペで動く:最初の失敗行を4種類に分類するスクリプト
証拠を取ったら、保存した build.log から最初の失敗候補を取り出して、さっきの4分類(型/依存・import/設定/環境)のどれに近いかをラベル付けします。scripts/triage-build-log.mjs として保存し、node scripts/triage-build-log.mjs build.log で動きます。
#!/usr/bin/env node
import { readFileSync } from "node:fs";
const logPath = process.argv[2];
if (!logPath) {
console.error("使い方: node scripts/triage-build-log.mjs build.log");
process.exit(1);
}
// 上から疑う順番(型 → 依存 → 設定 → 環境)でルールを並べる
const rules = [
{ name: "型エラー", regex: /is not assignable|Property .* does not exist|Type error|TS\d{3,}/i },
{ name: "依存またはimportパス", regex: /Cannot find module|ERR_MODULE_NOT_FOUND|Cannot resolve/i },
{ name: "設定またはfrontmatter", regex: /frontmatter|content collection|InvalidContentEntry|MDX|schema/i },
{ name: "環境差または権限", regex: /EACCES|EPERM|permission denied|ENOENT.*case|command not found/i },
{ name: "実行時のnull・形不一致", regex: /TypeError:.*undefined|undefined is not|Cannot read/i },
];
const lines = readFileSync(logPath, "utf8").split(/\r?\n/);
// 末尾ではなく「最初に」マッチした行を採用する
const firstFailure = lines.find((line) =>
/error|failed|ERR_|Cannot|TypeError|is not assignable/i.test(line)
);
const matchedRule = rules.find((rule) => firstFailure && rule.regex.test(firstFailure));
console.log(
JSON.stringify(
{
firstFailure: firstFailure || "明確な失敗行が見つかりませんでした",
bucket: matchedRule ? matchedRule.name : "手で読む必要あり",
nextDiagnostic: matchedRule
? "このbucketを証明/反証するコマンドを1つ実行してから、ファイルを編集する。"
: "最初の失敗行の手前30行を読んで、手で分類する。",
},
null,
2
)
);
この分類は完璧でなくて構いません。目的は自動修正ではなく、Claude Code への依頼を狭くすることです。「型エラー」と出れば型定義の確認から、「設定またはfrontmatter」と出れば本文より先に content.config.ts から手をつける。どこから見るかが決まるだけで、修正の精度が変わります。
Claude Codeに渡して直す反復の回し方
分類できたら、いよいよ Claude Code に渡します。ここで全文を貼らないのが肝心です。広いリファクタを禁止して、診断コマンドと証拠を必ず返してもらう形にします。
この失敗したビルドログを読んでください。
広いリファクタは提案しないでください。
まだファイルを編集しないでください。
返してほしい内容:
1. 最初に失敗した行
2. もっともありそうな原因を1つ(型/依存/設定/環境差のどれか)
3. その原因を確認する最小の診断コマンド
4. 許可する最小のコードまたは設定修正
5. 修正後の検証コマンド
6. PRコメントに残す「原因・変更・証拠」の3行
このプロンプトの強い点は、AIに「作業の順番」を渡していることです。原因が曖昧なまま修正へ進ませない。複数人で同じリポジトリを触っているなら、「関係ない slug・生成レポート・他人の差分は触らない」も足します。
反復はこう回します。①最小の診断コマンドを実行→②結果をAIに返す→③仮説が当たっていたら最小修正→④検証コマンドで証明→⑤通らなければ仮説を1つ変えて①に戻る。一度に何ファイルも直させないのがコツです。1ループ1仮説。これだと、どの修正が効いたのかが必ず分かります。
僕の体感だと、ログ全文を渡して一発で5ファイル直すより、このループを2〜3周したほうが、結局トータルで速い。差分が小さいのでレビューも一瞬で終わります。
最小再現を作ると、AIも自分も迷わない
切り分けが進まない時、効くのが最小再現です。落ちている部分だけを切り出して、それ単体でビルド・実行する。
たとえば型エラーなら、その型と値だけを別ファイルにコピーして tsc --noEmit を当てる。importエラーなら、その1行だけを書いた repro.mjs を作って node repro.mjs する。周りのノイズを全部はがすと、原因がむき出しになります。
# importが本当に解決できるかだけを最小ファイルで確認する
printf 'import x from "./対象モジュール.js";\nconsole.log(typeof x);\n' > repro.mjs
node repro.mjs
最小再現の良いところは、Claude Code に渡す情報量がぐっと減ることです。「このプロジェクト全体で undefined が出ます」より、「この5行だけで undefined が再現します」のほうが、AIは桁違いに正確に直します。人間が読むときも同じ。再現条件が小さいほど、原因の候補が減ります。
CIと手元の差を疑う時の手順
手元で通るのにCIで落ちる——この時こそ、コードに手を出す前に差を並べるのが鉄則です。Claude Code にも、修正ではなく差の洗い出しから頼みます。並べる項目はだいたい決まっています。
- Nodeバージョン:
node -vを手元とCIで比較。.nvmrcやenginesとCIの設定がズレていないか。 - OSと大文字小文字: macOS / Windows は大文字小文字を区別しないが、CIのLinuxは区別する。
import './Foo'と実ファイルfoo.tsの不一致は手元だけ通る典型。 - 環境変数: CIに secret が渡っているか。名前のtypo、空文字、未設定。
- 作業ディレクトリと書き込み権限:
permission deniedは実行ユーザーと書き込み先の問題のことが多い。 - lockfileとキャッシュ:
npm ciはlockfile厳守。手元のnpm installと挙動が違う。
この洗い出しを先にやると、「LinuxのCIだけ大文字小文字で落ちていた」みたいな原因が、コードを1行も触らずに見つかります。CIまわりの再発防止ルールづくりはCI/CDセットアップに、変更が本当に直ったかを残す習慣は検証レシート運用にまとめてあります。
公式の根拠も範囲を決めておくと混乱しません。Node側のエラーコード(ERR_MODULE_NOT_FOUND など)はNode.js Errors、Astroの frontmatter / content collection はAstro Content Collections、Claude Code 自体の接続や設定読み込みの疑いはClaude Code Error referenceを基準にします。
僕がやらかした切り分けの失敗3つ
正直に書きます。切り分けを覚える前、僕のビルド対応は事故だらけでした。
ひとつ目は、ログの末尾だけ見たこと。冒頭の金曜の夜がこれです。末尾のスタックトレースは、上流の import 失敗や frontmatter 失敗の“余波”でしかなかった。最初の error を保存せずに直したから、症状だけ隠れて翌週また出ました。今は何より先に「最初の失敗行」を保存します。
ふたつ目は、型エラーを as any で黙らせたこと。その場はビルドが通る。でも型が崩れた本当の場所は上流にあって、any は下流で握りつぶしているだけ。次の改修で別の場所が壊れて、原因がもっと分かりにくくなりました。型は遡って直す、が鉄則です。
みっつ目は、手元で再現しないのにコードを直そうとしたこと。CIだけで落ちるエラーに、ローカルで当てずっぽうの修正を積み上げました。再現しないんだから、直ったかどうかも分からない。環境差は、まず差を並べる。これを覚えてから、CI起因のエラーで消耗しなくなりました。
よくある質問
Q. 型エラーと依存エラー、どっちから直すべき? A. 型からです。型エラーはほぼ自分のコード内で完結し、原因が特定しやすい。依存・importはコードと外部の境目で、環境にも左右されます。上(型)から下(環境差)へ順に潰すと、ログが比較でき、迷宮に入りません。
Q. ログ全文をClaude Codeに貼ってはいけないの? A. 禁止ではありませんが、全文+「直して」は末尾のノイズに飛びつかれやすいです。最初の失敗行・仮説1つ・検証コマンド1つに絞ると、差分が小さくレビューも楽になります。エラー文の渡し方はエラーメッセージの読み方が詳しいです。
Q. 手元で通るのにCIだけ落ちます。最初に何を見る? A. コードではなく差です。Nodeバージョン、OSの大文字小文字、環境変数、作業ディレクトリと権限、lockfile。特にLinuxのCIは大文字小文字を区別するので、importのパスと実ファイル名の不一致が定番です。
Q. Cannot find module が出たら npm install でいい?
A. 早いです。まず node -p "require.resolve('パッケージ名')" で本当に解決できないか確認を。解決できるのに落ちるなら、importパスのtypoかESM/CJS混在を疑います。依存を上げるとlockfileやCIキャッシュまで波及します。
Q. 切り分けにどのくらい時間をかける? A. 僕は15分を目安にしています。最初の5分で証拠取り(編集しない)、次の5分で仮説を1つに絞る、最後の5分で最小修正と検証。15分で原因候補が1つに絞れなければ、最小再現を作って範囲を狭めます。
実際に試した結果
冒頭の“翌週また同じエラー”以来、僕はビルドが落ちても「早く通す」を最初の目標にするのをやめました。代わりに見るのは、最初の失敗行はどれかと、型・依存・設定・環境差のどれかです。
順番を固定しただけで、対応がぶれなくなりました。以前は undefined を見て広めの null check を足していたのが、frontmatter の欠落・生成ファイル漏れ・importパスのtypoに早く戻れる。CIだけで落ちる時も、コードを触る前に差を並べるから、消耗しなくなりました。
Claude Code への渡し方も、全文丸投げから「最初の失敗行・仮説1つ・検証コマンド1つ」に変えたら、返ってくる修正がほぼ最小になりました。最後に「原因・変更・証拠」の3行を残すと、次に同じエラーを見た未来の自分が一瞬で戻れます。賢いAIに全部任せるより、切り分けの順番という足場を先に作る。遠回りに見えて、これがいちばん速いというのが今の実感です。
エラー文の読解はエラーメッセージの読み方、症状から原因を挟み撃ちするデバッグはgit bisectのデバッグ手順へ。手を動かして覚えたい人は教材一覧に、チームでCI・権限・検証まで整えたいなら研修・導入相談が向いています。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。