きっかけ:dev server を立てたら @nuxt/content がスタックトレースを吐いた

朝、pnpm dev を叩いたら起動ログの中に @nuxt/content のスタックトレースが混じった。module.mjs:359 で database write が転んでいる。ブラウザで localhost:3000 を開くとページは出るけれど、コンソールには database lock 系の警告が並んで気持ちが悪い。

最初は「@nuxt/content のバグかな」と思いかけたが、dev server 自体は port 3000 で立ち上がって PID 41280 として動いている。プロセスは生きていてキャッシュ書き込みだけ転んでいる、というちぐはぐな状態だった。

原因を掘り当てる:裏で pnpm generate が走り続けていた

落ち着いてプロセス一覧を見たら、答えは別のところにあった。

  • pnpm generate が裏でまだ走っていた(13:25 起動、確認時点で数分経過)
  • generate は SSG ビルドで dev cache の SQLite を握る
  • そこに dev server が同じ SQLite に書き込みに行って locked になっていた
  • ついでに Playwright の test-server の残骸が 2 本残っていた

generate を途中で殺すか完走を待つかで迷ったが、約10分の処理がもう数分進んでいたので完走させる判断に倒した。pnpm generate の所要時間が読めるからこそできる判断で、ここで殺して再度回すと結局10分待つことになる。

動かしたフロー

  1. generate を完走させる(PID 8808 / 35300 が消えるのを待つ)
  2. 並列で、auto-import 衝突回避用のファイル修正を先に進める(generate には影響しない)
  3. generate 完走後、別の dev server (PID 44420) が port 3000 で起動済みなのを確認
  4. PID 44420 を停止(PID 0 は Windows の Idle Process なので無視)
  5. .nuxt 配下の dev cache を削除
  6. dev server をクリーン再起動

PowerShell でプロセスを止めるとき、最初に bash の Heredoc に $ を直書きしたら bash 側で展開されて空になった。シングルクォートで囲み直して通した。Windows + Git Bash + PowerShell が混ざる環境のいつもの落とし穴で、毎回引っかかる。

結果と副次的な発見

  • dev cache を消した状態で起動し直したら、database lock の警告は全消えした
  • Vitest で 17331 件のテストを回したところ、自分の修正と無関係な tokibo の noindex テストが 2 件失敗した(既存の問題で、今日の作業とは別件)
  • 修正に関するテストは全部 pass

ここで一旦区切って、関連する別の片付け作業に移った。

同じセッションでやった片付け:DEG_TO_RAD の降格

dev server をクリーン再起動するついでに、app/utils/analemma-claude.tsDEG_TO_RAD / RAD_TO_DEGexport const から内部 const に降格させた。

Nuxt の app/utils/** は auto-import 対象で、同じ名前の定数を別ファイルから export していると衝突する可能性がある。今回、別の analemma 系ファイルでも同名の定数を持つ必要が出てきたので、auto-import 対象から外して内部 const にした。

降格しても安全か確認した手順:

  • テストファイルは DEG_TO_RADRAD_TO_DEG も明示 import していない
  • なら降格しても影響範囲はない、と判断した

ここで安心したのがまずかった。あとで二発目を食らうことになる。

二発目:measure-deploy.ps1 がビルドエラーで止まった

15時43分、powershell -ExecutionPolicy Bypass -File scripts/measure-deploy.ps1 を回したら、ビルド途中で止まった。

DEG_TO_RAD is not exported by app/utils/analemma-claude.ts

降格させたばかりの DEG_TO_RAD を「is not exported」と言われている。意味が一瞬わからなかったが、直近コミット 67575b4fgit show させたら答えが出てきた。

app/pages/blog/analemma-claude.vue 側で DEG_TO_RAD を明示 import していた。 これを更新し忘れていた。

朝の確認では「テストファイルは明示 import していない」までしか見ていなくて、ページ側の明示 import を確認しそびれていた。Nuxt の auto-import を意識して utils 側ばかり見ていたのが盲点だった。

対応の判断:1案を捨てて2案を採った

選択肢は2つあった。

  1. 降格を撤回して export const に戻す: ビルドは通るが、重複 export 警告が出る可能性があり、降格コミットの意図に逆行する
  2. ページ側の明示 import を整理して、ページ内で必要な定数を直接定義し直す

採ったのは2案。apps/web/app/pages/blog/analemma-claude.vue 側での DEG_TO_RAD 使用は185-186行目の2箇所だけで、ページ内で完結させたほうが依存関係が浅くなる。降格の意図(auto-import 衝突回避)も保てる。

修正後にビルドを回し直したら通った。

学び:「export const → 内部 const 降格」は参照側を全部洗わないと事故る

今回の二発目は、auto-import 衝突を避けるために export を引っ込めた副作用そのものだった。

  • Nuxt の app/utils/** は auto-import 対象なので、export を外すと「auto-import を使っている呼び出し側」は IDE / Vue ファイルで即エラーになり気づきやすい
  • 危ないのは「明示 import しているファイル」のほう。auto-import に頼っていないので静的解析で目立ちにくく、ビルドして初めて落ちる
  • テストファイルだけ確認して安心したのが甘かった。ページコンポーネント・他の utils・composable・scripts まで grep -r "DEG_TO_RAD" apps/web/app で洗うべきだった

降格コミットを作るときの手順としてはこう固めた:

  1. 降格対象シンボルを grep -r で全参照箇所を出す(テストだけでなくページ・composable も)
  2. auto-import 経由か明示 import かを分類する
  3. 明示 import 側は降格コミットと同じコミットで更新する
  4. ビルドを1回通してから push する(dev での確認だけで安心しない)

朝の database lock のほうも併せて教訓を1個。「複数の重い裏プロセスが同じリソースを握っていないか」を、エラーログの内容より先に疑う癖をつける。@nuxt/content のスタックトレースが派手だったので原因が module 側にあると思いかけたが、実際はモノレポ内で2本の pnpm プロセスが SQLite を奪い合っていただけだった。

このあと残った宿題

  • 降格・export 削除をする系の作業に、参照側の grep を必ず挟むチェックリストを .claude/rules/ のどこかに残す
  • tokibo の noindex テスト 2 件失敗は既存問題として別途調査する