開発eurekapu-nuxt4

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 サイズ490KB18KB(3.7%)
10コース教材データTS 直 import 616KBpublic/ 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 — 引き継ぎドキュメント