5/7 から連続で落ち続けた SSG ビルド
eurekapu-nuxt4 の Cloudflare Pages デプロイが、5/7 以降ずっと exit 134 で死んでいた。Node のヒープ上限 2GB に到達して、SSG プロセスが力尽きる。3日連続で赤いバッジを見ていた。
最初は PR #18(cockpit モーダルズーム関連)のレビュー中に発覚した。PR の中身を読むと UI 改修だけで、ビルドが落ちる要素は一切ない。差分を main と比べて、ビルド失敗は main 側の既知 OOM だと切り分けた。PR #18 は技術的に独立してグリーン、ビルド失敗は別件。
「これ実質的にグリーンにする必要なくてもうスクワッシュマージしちゃいませんか」と判断して、PR #18 は素通りで取り込んだ。CI の赤が PR の問題でないなら、PR を待たせる理由がない。
Codex に調査指示書を書かせる
OOM の真因を掴むため、Codex に「5/6 成功と 5/7 失敗の間のコミット差分を読んで原因仮説を出して」と依頼した。出力は memo/2026-05-10/codex-build-oom-investigation.md に保存。
Codex が返した仮説はこうだった。
allMillerChapters.ts等の TS ファイルから Excel 教材の全文を直接 import している。Vite/Nuxt がこの 490KB 級のデータをメモリに展開した状態で SSG を回すと、ページ数 × データサイズで爆発する。目次・メタデータ(toc, slideCounts, manifest)と本文を分離し、本文は build 時に public/ へ JSON として吐き出して fetch で取りに行く構造に変えるべき。
提案は腑に落ちた。Excel 教材のページは10コースあり、各コースの全文を Vue の SFC が import で抱えていた。SSG はページごとに Vue コンポーネントを評価するから、メモリは積み上がる。
試行錯誤1: Codex CLI の Windows サンドボックスエラーを根本解決した
計画書を書いて Codex レビューにかけようとしたら、毎回 Codex CLI がサンドボックスエラーで落ちた。
最初は「リトライで通るかも」と数回叩いたが、毎回同じところで止まる。一旦回避策を考えかけたが、自分が「毎回エラーするので、根本的な解決策を考えてほしい」と Claude Code に投げた。
原因を追ったら ~/.codex/config.toml の設定に行き着いた。
[windows]
sandbox = "elevated"
elevated モードは Windows Sandbox/Hyper-V を使う特権分離方式で、ホストの仮想化機能が整っていないと起動できない。Claude Code が代わりに unelevated に書き換えた。
[windows]
sandbox = "unelevated"
バックアップを取得 → 設定変更 → テスト実行で、Codex CLI が一発で通った。回避策で済ませず、設定ファイルの根本に手を入れたから、以降のレビュー全部が走るようになった。
試行錯誤2: Codex 再帰レビューで計画を磨く
計画書 memo/2026-05-10/excel-data-separation-plan.md を Codex に3回読ませた。
- 1回目: 致命的指摘3点。本文分離の単位、Loader の責務、lint ルールの粒度。全部反映。
- 2回目: 新たな1点。「MillerViewer のナビ設計が全章 chapters を持っている前提で書かれているため、章別 JSON に分割すると次章ナビが壊れる」。これは見落としていた。
chaptersの代わりに manifest だけ持って、ナビは章 ID で順序を解決する設計に書き直した。 - 3回目: 致命的な点なし。計画確定。
「人間が判断する係、Codex が指摘する係」の構図がはまった。自分は方針判断と取捨選択だけして、計画書の整合性チェックは Codex に回した。
Phase 1〜7 の実装
計画が固まったら、Claude Code に Phase 1〜7 を一気に実装させた。
- Phase 1:
excelBasicFunctionsNarration.tsを生成スクリプトで JSON 化。巨大データを TS から分離した。 - Phase 3:
top.vueの import を軽量 manifest 形式に変更。490KB が 18KB に減った(3.7%)。SSG 時のメモリ展開がここで一段沈む。 - Phase 4: ESLint の
no-restricted-importsで、MillerViewer から教材全文 import を機械的に禁止した。再発防止の網。 - Phase 5:
MillerViewerLoader.vueを新規作成。既存 MillerViewer はそのまま温存して、Loader が JSON を fetch して props で渡すラッパー方式にした。リスク最小の差し込み。 - Phase 7: 10コース全部を JSON 化。616KB が public/ 配信に逃げた。全10ページを Loader 経由に書き換え。
数字は手応えがあった。top.vue の manifest が 490KB から 18KB に痩せたとき、Vite の HMR が体感で速くなった。
試行錯誤3: SSR で fetch が 404 を返した
dev server で動作確認したら、CSR では動くが SSR で 404 が返ってきた。
原因を Codex に投げたら即答。「SSR からの fetch が server middleware に阻まれている。Cloudflare Pages 環境では public/ の JSON も静的アセットなので、SSR 経由で取りに行くには server API ルートを噛ませて static asset binding 経由で読むほうが安全」。
server/api/content/excel/[...].ts を新規追加した。
export default defineEventHandler(async (event) => {
const path = getRouterParam(event, '_')
const assets = event.context.cloudflare?.env?.ASSETS
const url = new URL(`/excel/${path}.json`, event.node.req.url)
const res = await assets.fetch(url.toString())
return res.json()
})
ところがこれを書いて dev server をリロードしても、API ルートが 404 のまま。新規 server routes は HMR で取り込まれない仕様で、dev server 自体を再起動しないと反映されないらしい。
ここは自分が手で踏んだ。「今止めて再起動しました」と Claude Code に伝えて、もう一度確認させた。
SSR HTML を grep したら、Excel 教材本文「ウィンドウ枠の固定」の文字列が 4 件含まれていた。動的 useHead も manifest meta から正しく生成されている。SSR が通った瞬間だった。
試行錯誤4: CI failure を OOM だと誤診断した反省
push したら CI が失敗した。「まさかの OOM 再発」と思い込んで、応急処置で NODE_OPTIONS=--max-old-space-size=6144 を CI に追加してしまった。当初 2GB のヒープ上限を 6144MB に引き上げる、力技の延命。
push 後に自分が「まだ根本対応してください」と指示して、ログをちゃんと読み直した。OOM ではなかった。実は既存の Stripe webhook unit テストが別件で落ちていて、Build フェーズ自体は成功していた。
「失敗バッジ = OOM 再発」と直感で結びつけて、ログを読まずに NODE_OPTIONS をいじった。push を取り消すほどではないが、診断の順序を飛ばした反省として記録しておく。引き継ぎドキュメントを memo/2026-05-10/session-handover.md に残した。
数字で押さえた成果
| 指標 | 変更前 | 変更後 |
|---|---|---|
top.vue import サイズ | 490KB | 18KB(3.7%) |
| 10コース教材データ | TS 直 import 616KB | public/ JSON 配信に分離 |
| Codex 再帰レビュー | — | 3回で致命的指摘ゼロ |
| ビルド OOM ヒープ上限 | 2GB(デフォルト) | 6144MB(応急処置として残置) |
人間が判断、AI が実行の構図
このセッションで自分が判断した箇所は数えるほどしかない。
- PR #18 を独立判定して「スクワッシュで通す」と決めたこと
- Codex サンドボックスエラーで「根本対応してくれ」と方針を切ったこと
- SSR 404 で dev server を手で再起動したこと
- CI failure で「OOM じゃなくテスト失敗だ」と切り分け直したこと
それ以外、つまり Codex の調査指示書、計画書の3回レビュー、Phase 1〜7 の実装、Loader 設計、server API ルート、引き継ぎドキュメントは全部 Codex と Claude Code が回した。
税理士・会計士業務に置き換えるなら、「巡回監査で違和感を拾う係」と「資料突合・整合性チェックを回す係」の分業に近い。違和感を拾う筋肉だけ落とさず鍛えれば、突合作業は AI に積める。
残タスク
-
NODE_OPTIONS=--max-old-space-size=6144を外す(教材分離だけでビルドが通るはずなので、本来は 4096 か 2048 で再検証したい) - Stripe webhook unit テストの修正(別 issue)
- MillerViewerLoader の SSR キャッシュ戦略(現状は毎リクエスト fetch)
参考メモ
memo/2026-05-10/codex-build-oom-investigation.md— Codex 調査指示書memo/2026-05-10/excel-data-separation-plan.md— 実装計画書(3回レビュー反映済み)memo/2026-05-10/session-handover.md— 引き継ぎドキュメント