開発mdx-playground

912件の自己ループリダイレクトとOG画像署名切れを一気に直した日

本番障害が2件、ほぼ同時に発覚した。どちらも自動テストでは拾えていない領域で、私がブラウザの画面を見て「あれ?」と気付いた。

1件目は log.eurekapu.com を開いて、公開したはずの記事ページが404で返ってきたこと。2件目はXに記事リンクを貼ってカードが favicon の謎図形に化けていたこと。

調査と修正のほぼ全てをClaude Codeに投げ、私は方針判断とセキュリティリスクの最終確認だけをやった。終わってみればデプロイ時間が473秒から293秒に短縮されて、副産物まで付いてきた。一日の流れを記録に残す。

障害1: 912件の自己ループリダイレクトが本番を覆っていた

朝、自分のブログ log.eurekapu.com で記事URLを踏んだら、ブラウザが404を返した。直前にデプロイした記事ではない。何ヶ月も前に書いたはずの記事が本番で表示されない。

Claude Codeに「公開済みの記事が本番で404になっている。原因を調査して」と頼んだ。リダイレクト設定を疑い apps/web/public/_redirects を読ませると、ファイルの行数が異常に膨らんでいた。

Claude Codeに scripts/generate-redirects.mjs の生成ロジックを追わせると、frontmatter の path がファイル名 slug と一致する記事に対して、/foo → /foo という自己ループルールが機械的に生成されていた。その数、912件

Cloudflare Pages の _redirects は上から順にマッチするので、自己ループに当たった記事はリダイレクトループ扱いで本番からアクセス不能になっていた。912件分の記事が機械的に水没していたことになる。

修正は単純だった。Claude Codeに「frontmatter の path がファイル名 slug と一致する場合は出力をスキップして」と指示。スクリプトに数行のガード句を足して、自己ループは0件になった。

そのままだと再発する。テストファースト派の同僚のフリをして「リグレッション防止のテストを書いて」と続けて頼んだ。tests/redirects-no-loop.test.ts が5件、その場で生まれた。 pnpm test:run で全部 pass した。

副産物として、デプロイ時間が473秒から293秒に縮んだ。Cloudflareにアップロードする _redirects の行数が912行減ったので、upload phase が38%短くなった計算になる。「直したらビルドが速くなる」という体験を、初めて測定値で見た。

障害2: OG画像が favicon.svg にフォールバックしていた

午後、自分の記事リンクをXに貼って投稿プレビューを開いたら、カード画像の場所に小さな謎の図形が並んでいた。 favicon.svg がOG画像として読まれている。

OG画像はNuxtサイトとは別の Cloudflare Workers で生成している。Claude Codeに「OG画像がfaviconにフォールバックしている。 useOgSignature.ts のフォールバック条件を調査して」と振った。

数分後、犯人が報告された。 apps/web/.envOG_SECRETdev- で始まる文字列になっていて、 useOgSignature.ts の「dev- 始まりなら署名なしでフォールバック」という条件にぶつかっていた。本番で署名が生成されず、Worker側の検証が落ち、Workerは favicon を返す経路へ逃げていた。

「いつから壊れたのか」を Claude Code に git blame で追わせた。 apps/web/.env 自体は .gitignore 配下だが、 .env.exampleuseOgSignature.ts の変更履歴をたぐると、2026-03-17.env へダミー値の dev-*** を入れたまま放置していたことが浮かび上がった。2ヶ月半、OG画像は壊れていた。Xで誰もカードを共有していない理由がやっと分かった。

私は途中で1つ確認したいことがあって、Claude Codeに手を止めさせた。

ローカルでビルドしてからデプロイしているのに、なぜCloudflare側に環境変数を仕込む必要があるんだ?

Claude Codeが整理した答えはこうだった。「NuxtのSSGはローカルでビルドした静的HTMLをCloudflare Pagesにアップロードするので、Cloudflare側の環境変数は要らない。一方、OG画像生成は Cloudflare Workers 配下の動的ワーカーで動いているので、こちらは Cloudflare Workers の secret に OG_SECRET を仕込む必要がある」。同じCloudflareでも、Pages と Workers の運用は別だった。納得して先に進んだ。

次のチェックポイントは secret rotation だった。Claude Code に wrangler secret put OG_SECRET を打たせるかどうか、ここは私が止まって考えた。AIに本番secretを扱わせるのは、リスクをどう見積もるか次第で判断が分かれる。

セキュリティリスクをClaude Code自身に整理させた。「値はランダム生成 → stdin パイプで wrangler に流す → ターミナルにもログにも値を出力しない設計にすれば、リスクは低〜中。緩和策として、生成と投入を同一コマンド内で完結させ、値を保持する変数も最後に上書きする」との回答が返ってきた。

私はこの設計に納得して「やっていい」と告げた。Claude Codeが secret rotation を代行し、値は最後まで私の目にも端末にも残らなかった。Nuxt側の .env も同じ値で更新させ、ローカルビルドと本番Workerで署名が一致する状態に揃えた。

ビルドして本番にデプロイした。Facebook の Sharing Debugger では新しいOG画像が表示された。Xはキャッシュが古くて反映に時間がかかったが、画像のURL自体は正しく生成されていた。

最後にClaude Codeに scripts/verify-og-images.mjs を書かせ、 measure-deploy.ps1 の prerender 完了直後と deploy 直前にフックさせた。最新50記事のOG画像URLを叩いて、favicon にフォールバックしているものが1件でもあれば exit 1 で止める設計にした。これで2026-03-17のような事故は二度と気付かないままにはならない。

「ユーザーの目視→AIに調査させる→根本原因→デプロイ」の構図

今日の2件は、どちらもブラウザを開いた私の目で気付いた。自動テストでは検知できていない領域だった。

自己ループは「設定ファイルが912行膨らんでいる」状態だったが、ビルドは通っていた。OG画像は「favicon にフォールバックしている」状態だったが、500エラーは出ない。どちらも「動いてはいるが間違った結果を出している」事例で、ここは目視レイヤーが守るしかない。

一方、原因の特定では私の出る幕がほとんどなかった。 git blame で「いつから壊れたか」を辿る作業、generate スクリプトを読み込んでロジックの欠陥を抜き出す作業、 .env の値が条件分岐のどこに引っかかっているかを見つける作業。全部Claude Codeに任せた。

私が動いたのは、

  • 「OG画像をAIに secret rotation させるリスクをどう見るか」の判断
  • 「Pages と Workers の運用が別である」事実の確認質問
  • 「リグレッション防止のテストを書け」の指示

の3点だった。人間は判断する係、AIは実行する係の役割分担が、今日は特にきれいに出た。

税理士・会計士フォロワー向けの応用

クライアント環境で「先月までできていた処理が今月できなくなった」と問い合わせが来る場面はよくある。先方の画面共有を見て「動いてはいるが結果が違う」状態を目視で拾うところまでは、AIには代行できない。

しかし、原因の特定はAIに振れる範囲が広い。会計ソフトの設定変更履歴を遡る、特定の科目だけ集計対象から外れている条件を探す、エクスポートCSVのスキーマがいつから変わったかを git blame 相当の手段で追う。こうした「いつから・なぜ」の調査は、人間が手で全件追うとしんどい部分で、AIに方針だけ伝えれば動く領域でもある。

そして、その判断結果に基づいてクライアントの環境を実際に書き換える時、API キーや顧問先パスワードの rotation のような神経を使う作業をAIに代行させるかどうかは、税理士の責任で判断する場面になる。「値を一切表示させない設計でなら任せる」「人間が手で打つ」「そもそも触らせない」の三択を案件のリスクに応じて切り分ける形になる。

私が今日Claude Codeに secret rotation を任せたのは、 OG_SECRET の漏洩リスクが「OG画像の署名が偽造される」程度に留まり、金銭被害につながらない範囲だと判断したからだった。クライアントの会計データを触らせるかどうかは、別のリスク評価が要る。

今日のまとめ

  • 912件の自己ループリダイレクトを排除し、デプロイ時間が473秒から293秒に短縮した
  • OG画像のフォールバック条件にぶつかっていた OG_SECRET を rotation で更新し、Facebookでカード表示を回復した
  • 2026-03-17から2ヶ月半壊れていた事実をAIに git blame で特定させた
  • リグレッション防止に tests/redirects-no-loop.test.ts 5件と scripts/verify-og-images.mjs を追加した
  • 本番secretをAIに代行させる判断は私が下し、実行はClaude Codeが担った

ユーザーの目視で発覚した本番障害を、根本原因の特定と修正までAIに振り、人間は判断とリスク評価だけに集中する。今日の運用パターンは、明日以降の他の障害対応にもそのまま使い回せそうだった。