散らかってきたから一掃したくなった
朝、git log をスクロールしながら直近のコミットを眺めていたら、機能追加と応急処置が層のように重なっていることに気づいた。Stripe Webhookの周辺、認証ミドルウェア、Excel関連ビュー、テストの skip 行。ファイルを開くたびに「ここ前から気になってた」が積み上がっていた。
そこでターミナルに一言だけ投げた。
「コードが散らかってきたから vitest / playwright / coverage で大掃除したい。サブエージェントを並列に派遣して、構造・セキュリティ・パフォーマンス・SRE の4視点でレビューさせて」
人間がやったのはここまで。あとは Claude Code が4本のサブエージェントを同時に立ち上げて、それぞれの視点で apps/web/ を走り回り始めた。
並列レビュー → Codex 3回ループの流れ
ステップ1: サブエージェント4本を派遣
4視点のレビュアーを並列に走らせた。
- 構造レビュー: ディレクトリ責務、循環依存、命名の一貫性
- セキュリティレビュー: 認証境界、入力検証、シークレット露出
- パフォーマンスレビュー: N+1、不要な watch、バンドル肥大
- SRE レビュー: エラーハンドリング、ログ、Cloudflare 環境での挙動
各レビュアーが Markdown レポートを書き出したら、それらを統合した総合レポートを memo/2026-05-18/code-review-comprehensive.md に1本にまとめてもらった。指摘は Critical / High / Medium の3階建てに分類した。
ステップ2: Codex に「致命的なものだけ言って」と頼む
統合レポートと優先順位の妥当性を Codex (gpt-5.5) にレビューしてもらった。1回目で返ってきた指摘が刺さった。
「PERF系をCriticalに入れているが、これは緊急度が下がる。本当に直さないと事故るのはアトミック性が崩れている Stripe Webhook と、認可フラグの既定値、依存ライブラリの CVE の3つだ」
優先順位を間違えていた。PERF を Critical から下ろして、見落としていた依存パッケージの CVE を Critical に繰り上げた。
その後、修正方針を反映するたびに codex exec resume --last で文脈を引き継ぎながらレビューを回し、3回目でようやく「致命的な指摘なし」が返ってきた。Critical の輪郭がここで固まった。
Critical 3件で実際に直したもの
Stripe Webhookのアトミック化
Webhookでサブスクリプション情報を反映するときに、複数テーブルへの更新を逐次走らせていた。途中で落ちると一部だけ書き込まれた状態でDBが残る、典型的な事故ポイント。D1 の batch() でまとめて投げる形に書き換えた。
await db.batch([
db.prepare('UPDATE subscriptions SET ...').bind(...),
db.prepare('UPDATE users SET ...').bind(...),
db.prepare('INSERT INTO webhook_events ...').bind(...),
])
「Stripe側でリトライされる前提でも、自分側のDBが整合してないとリトライがさらに事故を呼ぶ」というレビュー時の指摘がそのまま腑に落ちた。
purchaseGating の既定値を true に
機能ゲートの設定が「環境変数を読んで、未定義なら false にフォールバック」という実装になっていた。本番で環境変数が剥がれた瞬間、有料機能がすべて開放される構造。未設定 = 安全側(true でゲートをかける) に既定値を寄せて、明示的に false を入れない限り解除されないように直した。
rollup を 4.57.1 → 4.60.4 へ
依存ツリーの中に残っていた rollup の旧バージョンに CVE が紐づいていた。pnpm why rollup で経路を確認しつつ、pnpm.overrides で最新へ寄せた。
High 8件で直したもの
| 領域 | 内容 |
|---|---|
| Stripe checkout / portal | origin 解決を Origin ヘッダ → 環境変数 → デフォルトの順に直し、全体を try/catch で包んでエラーを構造化ログに落とした |
| /api/admins | セッション認証だけだったところに、admin ロールの二重チェックを追加 |
| ExcelHtmlViewer | サーバー側で生成された HTML をクライアント描画する経路に、script/iframe 等を弾く簡易サニタイザを通した |
| better-auth singleton | テスト/本番で D1 binding が混ざる懸念があったので、WeakMap で binding ごとにインスタンスを分離 |
| 構造化ログ | Cloudflare Logs から拾いやすいよう event/level/userId/traceId を統一フィールドで出すように整備 |
| /api/health | DB と KV だけ見ていたところに Stripe API への疎通チェックを追加 |
Medium 7件は割愛するが、内部APIのレスポンス型を Zod で固定したり、不要な watch を computed に置き換えたりといった「気持ちのいい掃除」をまとめて入れた。
テスト基盤の修復で見えたバグ
ここからが本日のハイライト。Critical / High を直し終えたあとで pnpm test:run を走らせたら、テストが大量に落ちていた。
i18n プラグインがテスト環境で起動していなかった
落ちていたテストを1本ずつ追ったら、i18n プラグインがテスト環境では nuxt config を読み込めずに undefined.locales を参照していた。vitest.config.ts で nuxt の override を入れて、テスト時は最小構成の i18n だけ立ち上がるようにした。
skip されていた auth.test.ts を起こした
describe.skip がついた認証テストが7件あった。コミットログを辿ると「環境の問題で動かないから一旦 skip」というメモ。今回 better-auth の WeakMap 分離を入れた副作用で、skip を外しても全件パスした。
結果として:
- Test Files: 8 失敗 → 0 失敗
- Tests: 1738 → 1788 全件パス(skip 解除分を含む)
「section が違うのに slug が同じ」を発見
quizTopicsDisplayNumber.test.ts が1件落ちていた。期待値と実値を見比べたら、別セクションに属する問題が同じ slug を持っていた。実装ではなくテスト側の期待値が現実に追いついていなかったパターン。テストデータを正して通した。
カバレッジ取得で詰まったところ
pnpm test:coverage を v8 並列で回したら、hook timeout が再発してプロセスが固まった。以前にも見た症状で、vitest.config.ts で singleThread: true に切り替えて回避。並列度を犠牲にする代わりに、coverage/index.html が確実に生成された。
app/utils/** と app/composables/** のカバレッジを眺めて、薄かったところに失敗ケースを足した。100% は目指さず、触った関数のエラーパスだけ埋めた。
ビルドエラーの最後の障害物
最後に pnpm build を回したら、Vue の SFC パーサーが意味不明な位置で Unexpected token を吐いた。エラー行をクリックして飛んだ先は ExcelHtmlViewer.vue のコメント。
<script setup lang="ts">
// 例: 元HTMLに含まれる </script> を弾く
// ↑ このコメント内の閉じタグを Vue パーサーが script 終端と誤認していた
</script>
コメント内に書いた </script> という文字列を、Vue の SFC パーサーが本物の script 終端タグとして拾ってしまっていた。コメントを「閉じスクリプトタグ」と書き換えて回避。ローカル pnpm build が緑になった。
ただし Codex のサンドボックス側ではビルドが commonjs--resolver spawn EPERM で落ちる。最初は実装の問題かと疑ったが、ローカルの build / dev / test が全て通っていることから、サンドボックス側のプロセス起動権限の問題と切り分けた。実装にはこれ以上手を入れない判断にした。
学び
- 派遣する係に徹する: 4視点のサブエージェント並列も、Codex の優先度レビューも、「自分でレビューする」と思っていた頃の半日仕事が、判断を投げる側に回ったら2時間で終わった。
- Codex の優先度突っ込みが効く: Critical / High の分類は自分一人だと甘くなる。「PERFはCriticalじゃない、本当に事故るのはこれだ」と外から指差してもらえるのが大きかった。
- 「致命的なものだけ言って」と添える: Codex はクソリプ気質があるので、最初の一言で「瑣末はいい、致命的だけ」と縛ると一気に使える助言が増える。
- skip テストはリファクタの副作用で蘇る: 環境が変わった時に skip が外せるかを毎回見直すと、過去の自分の妥協を回収できる。
- コメント内の
</script>は地雷: 仕様としては既知だが、レビューで増えがちな箇所なのでマクロで弾くより</script>と書く運用にした。
明日は coverage が薄いままの app/composables/payments/** を埋めにいく。今日の Critical / High 修正でテストを足したが、エラーパスがまだスカスカ。