← AIバイブコーディングTips 一覧へ Publishing Pipeline / Root Cause Analysis

「404」の裏に、3つのバグが積み重なっていた

姉妹メディアSURPLUS(経済学の読み物サイト)で、全102記事のうち53本の記事間リンクが本番で404を返していた。1つ直せば終わると思っていたが、掘るたびに別のバグが出てきた——独立した3つの不具合が積み重なって発覚を遅らせていた実例と、承認ゲートを保ったままの再発防止設計。

この記事で手に入るもの

「1つの症状の裏に複数の原因が重なっている」ケースを実際に切り分けた手順と、アクセシビリティチェックが構文的には正しいリンク切れを検出できないという盲点。そして自動修復を組み込みつつ、外部への公開操作だけは人の承認を残す切り分け方の実例

01 — 何が起きたか

1つ直したら終わり、のはずだった

SURPLUS(姉妹メディア、経済学の読み物サイト)の内部リンク切れは、以前にも一度大がかりに修復したことがあった。今回はその後の棚卸しで、全102記事のうち53本でまだ本番404が起きていることに気づくところから始まった。

1つ原因を潰せば収まると思っていたが、修復を進めるたびに別の種類の不具合が姿を現した。最終的に、症状は「本番404」というひとつの現象なのに、原因は独立した3つのバグが積み重なったものだったと分かった。


02 — 3つのバグ

構文的には正しいのに、動かない

3つとも「HTMLとしては文法違反ではない」ため、通常のチェックをすり抜けていた点が共通している。

01

ディレクトリ形式のリンクが403を返す

href="../ArtN/"という末尾スラッシュ形式のリンクが、38記事・92本残っていた。実体はArtN/article.htmlにあり、ディレクトリインデックスが存在しないため本番では403になる。リンクとしての構文は正当なので、目視でもエディタの構文チェックでも異常に見えない

02

リンク先は正しいのに、何のリンクか読者に伝わらない

「第N回」のような通し番号だけのアンカーテキストが53本見つかった。リンク自体は生きているので404にはならないが、読者はクリックするまで何の記事かわからない。これは壊れているとは言えないが、機能していない状態だと判断した。

03

根本原因:投稿処理が書き換えるのは日付だけだった

SURPLUSも、このブログ(FLOW)と同じqueue/NN_slug/キュー投稿の設計で動いている。投稿スクリプト(publish_next.ps1)がキューを正式な記事番号(ArtN)へ移すとき、書き換えているのは投稿日(post-date)だけだった。記事本文中の相互リンクは、執筆時点の仮スラッグ../NN_slug/を指したまま公開されていた。102本中53本がこれに該当し、うち一部は読者に生のスラッグ名(例: 88_korea)がアンカーテキストとしてそのまま露出していた。

ここが核心。①と③はどちらも本番で404/403になるが、HTML構文としては正当なため、記事のアクセシビリティチェック(画像のalt属性やコントラスト比などを見る仕組み)の対象外だった。数ヶ月ものあいだ、正しいチェックを毎回パスしながらリンクだけが壊れ続けていたことになる。


03 — 検出と修復

対応表を作り直し、機械的に潰す

仮スラッグと公開後のArtNを結ぶ対応表がどこにも残っていなかったため、まずslug_ledger.jsonという台帳を新設し、ROADMAPの記載と公開済み記事の<title>を1件ずつ照合して102件分を復元した。

この台帳をもとにtools/fix_internal_links.pyという検査・修復スクリプトを書き、機械的に修復した。結果は次のとおり。

汎用アンカーテキストの53本は、1件ずつ文脈を確認した。「留保価格」「位置財理論」のようにトピック語が含まれる20本は書き換え不要と判断し直した——検出条件が文字数ベースだけの粗いものだったため、内容のある表現まで拾っていたと分かったからだ。真に内容のない33本だけを、本文で既に使われていたhouse style(序数+記事タイトル)へ統一した。


04 — 再発防止

自動化するのは「検出と記録」まで

根本原因(投稿処理が日付しか書き換えない)を直さない限り、次に公開する記事から同じ不具合がまた増え続ける。ここはユーザーの承認を得たうえで、投稿処理そのものに手を入れた。

組み込んだのは、投稿処理が post-date を書き換えた直後に「slug_ledger.jsonへ1行追記 → fix_internal_links.py --applyを実行」という2ステップ。ただし全自動にはしていない。修復に失敗しても警告を出すだけで公開処理は続行し、修復の影響で公開済み記事の内容を変える必要が出た場合はpending_reupload.txtにファイル名を書き残すだけで止め、本番サーバーへのアップロード(FTP)は自動実行しない。

この切り分けが要点だと考えている。ローカルでの検出・記録・書き換えは自動化してよいが、外部に公開される操作の最終トリガーは人が引く——このブログの運用ルールとして最初から決めていた線引きを、修復の仕組みにもそのまま適用した形になる。


05 — おまけの発見

検証中に見つかった、もう1つのバグ

この修正を検証している最中、副産物として別の不具合にも気づいた。Windows PowerShell 5.1は、BOM(ファイル先頭の目印バイト)が付いていない.ps1ファイルをANSIとして読み込み、日本語部分が文字化けする。投稿処理のスクリプト本体はBOM付きで保存されていることをバイト列で確認できたので実害はなかったが、もし今後どこかでBOMなしのまま日本語コメントやエラーメッセージを含むスクリプトを保存すると、無人で動くパイプラインがエラー時に化けた文字列しか吐かず、原因が読めないまま止まるリスクがある。正常系・異常系の両方を実際に切り出してテストし、影響がないことを確認したうえで元の状態に戻した。


06 — 自分の運用に当てはめる

持ち帰れる3点

以前に「記事番号は公開されるまで確定しない」という設計そのものの不具合を修復した記事を書いたが、今回はその続き——同じ根本原因が、より広い範囲で、複数の症状に分かれて残っていた実例になる。

1つの症状に、1つの原因とは限らない直しても直らないときは「まだ別の原因が残っている」と疑う前提で切り分ける。
「構文的に正しい」チェックは「動作として正しい」を保証しないアクセシビリティチェックのように別目的のチェックが、たまたまリンク切れを見逃す穴になることがある。
自動化の境界線は「検出・記録」と「外部への公開操作」の間に引く修復ロジックは自動実行してよいが、本番サーバーへ反映する最終操作は人が検品してから引く、という線引きを崩さない。