TL;DR
- 2026-09-10 に起きた 1 件の本番障害について、上流(記事を書く側)と下流(公開先を運用する側)が独立に実測し、互いの誤りを 4 件訂正しました。うち 3 件は、突き合わせが無ければ残っていました(残る 1 件は上流の自己訂正)。4 件は私が数え上げたもので、発生率ではありません。
- 4 件のうち 2 件は、相手側のリポジトリの一次資料を読んで初めて出ました。 自分の側だけを深く測っても出ません。残る 1 件は、どちら側にも無い外部の一次ソースで出ました。
- 基準時点: 2026-09-10。上流リポジトリ(本ブログ)は
origin/main=a184564(本記事を追加する前の記事 224 本)。 下流リポジトリ(公開先)は private なので、その SHA・PR 番号・タグ・ホスト名は本記事に一切書きません。書くのは挙動と手順の構造だけです。 - 訂正が落ちた理由は「よく話し合ったから」ではありません。測っている対象が違う / 測った時刻が違う / どちらも自分の側の一次資料しか持っていない、という 3 つのズレが、誤診を別の角度から照らしたためです。
- 持ち帰れる操作は 1 つです。主張を渡すときに、実測値と「どうやって測ったか」と「いつ測ったか」を必ず添える。 これが無い主張は、相手が再現できないので訂正できません。訂正されない主張は、正しいのではなく検査されていないだけです。
はじめに:2 人いても、同じ場所を見ていたら意味がない
「レビュアーを増やせば誤りが減る」はよく言われます。ですが増やしただけで減るなら、同じ資料を 2 人で読めばよいことになります。実際には、同じ資料を同じ時刻に読んだ 2 人は、同じ誤りを 2 回するほうが普通です。
この記事は、そうではなかった実例の記録です。1 件の本番障害について、2 つの独立した作業主体が、別々の対象を、別々の時刻に、別々の一次資料で測りました。そして互いの結論を 4 件訂正しました。うち 3 件は、突き合わせが無ければ訂正されないまま残っていました(残る 1 件は、上流が自分で測り直して自己訂正できたものです)。
登場するのは 2 つのリポジトリです。役割名で書きます。
| 役割 | 何をしているか | 手元にある一次資料 |
|---|---|---|
| 上流 | 記事を書き、公開先へ渡す | 記事の本文、記事側の検査スクリプト、公開先を外から叩いた HTTP 応答 |
| 下流 | 公開先のアプリケーションを運用する | 依存の pin、リリース履歴、外形検査スクリプトとその起動条件、ポストモーテム |
独立の単位はこの 2 つです。上流の内側には執筆とレビューの工程がありますが、どちらも上流の一次資料を共有しているので、独立な観測者としては 1 つと数えます(第 2-4 節に「上流のレビュアー」が出てきますが、これは上流の中の工程です)。
どちらも private リポジトリです。下流の PR 番号・commit SHA・リリースタグ・ホスト名・ワークフロー名・内部ドキュメントのパス、そして稼働中の依存バージョンは 1 つも書きません。 最後の 1 つは、この記事を書いている途中に禁止リストへ足しました。識別子だけを禁止していると、識別子ではないが同じくらい危ない値が素通りします(詳細は第 5 節)。上流(本ブログ)側の識別子だけは、基準時点を固定するための SHA と、第 5 節で 1 度だけ使う台帳 ID を書きます。どちらも読者が追える先ではないので、それ以上は増やしません。
1. 何が起きたか(1 段落で)
公開先のフレームワークの pin を前進させたところ、トップページ / だけが HTTP 500 を返し、記事ページと一覧は 200 のままでした。同日中に切り戻して復旧しています。この障害そのものの分析は本記事の主題ではありません。バージョン番号は書きません(理由は第 5 節)。主題は、その障害について 2 者が出した結論のうち 4 つが間違っていて、それが突き合わせで落ちたことです。
2. 訂正された 4 件の誤診
以下、各項の「確認手段」は私が 2026-09-10 に実行したコマンドそのものです。下流の資料はすべて読み取りのみで、ファイルは 1 文字も変更していません。作業ツリーは常に stale なので、参照はすべて git show <ref>:<path> の形で ref を明示しています。
2-1. 上流「404 を返すべき URL が 500 を返す。サイト側の恒久的な欠陥だ」→ 誤診
上流は障害の最中に公開先を外から叩き、存在しないはずの記事 URL が 500 を返すのを観測しました。そこから「404 ハンドラが壊れている恒久的な欠陥」という結論を出しました。
訂正: 恒久的な欠陥ではありません。測った時刻が障害の最中でした。
復旧後に同じ URL を測ると 404 が返ります。上流自身が復旧後に取り直した実測がこれです(公開先は既定 UA の HTTP クライアントにチャレンジを返すため、ブラウザを起動して同一オリジンのページ内から fetch() を発行して測っています)。
| パス(2026-09-10、復旧後) | ステータス |
|---|---|
/ | 200 |
/articles | 200 |
/articles/<存在しない slug> | 404 |
「未公開の記事の URL」も本来ここに並べたい行ですが、この日は測れませんでした。 第 2-4 節に書くとおり、下流の未公開記事の在庫がこの時点で 0 件で、「未公開だから 404」になる URL が 1 本も存在しなかったからです。存在しない slug の行と区別が付かないので、行そのものを置きません。 測っていない行を測った行と同じ表に並べると、測った値の数が水増しされます(第 3-2 節で自分に課している規則です)。
500 の正体は 404 ハンドラではありません。下流のポストモーテム(同一シグネチャの先行事象、2026-04)が特定している機序は、ビルド成果物がランタイムで動的な require を期待し、実行環境がそれを実装していないため、ワーカーの初期化時に例外が投げられてハンドラが 500 で終わるというものです。初期化が落ちるので、その実行経路に載る応答はすべて 500 になります。 404 応答を組み立てる not-found ハンドラも、その経路の上にいます。一方で記事ページと一覧が 200 のままだったのは、静的アセットとして配信されるルートがこの実行経路を通らない構成だったためです(先行事象のポストモーテムは、これを「可能性あり」と留保付きで記録しています)。今回の事象で、どのルートがどちらの経路に載っていたかは切り分けていません。 第 2-2 節に書くとおり、/ が 500 になった直接の機序も実行時ログでは捕捉されていません。ここで説明を確定させないでください。
ただし 404 については、障害の最中にその URL が実際に 500 を返していたこと自体が、404 応答が落ちている側の経路に載っていた証拠になります(静的に配信されていたなら 404 のまま返っていたはずです)。
構造として確かめられる部分だけを書きます。下流のアプリケーションを読み取りのみで参照したところ、not-found ハンドラはアプリのルート直下に 1 つだけあり、トップページと同じルートレイアウトを共有していました。トップと 404 は別々に壊れうる 2 つの部品ではなく、同じ入口の上に乗った 2 つの出口です。
# 下流リポジトリを読み取り専用で参照する(ref を明示。作業ツリーは stale)
git ls-tree -r --name-only <下流の ref> | grep -i not-found
# => ルート直下の not-found ハンドラが 1 件のみ
そして復旧後の応答ヘッダは、この 2 つが同じ扱いを受けていることを外から示しています。/ と 404 応答だけが x-nextjs-cache: HIT と x-nextjs-prerender: 1,1 を返し、記事ページ・一覧・タグは x-nextjs-prerender: 1 だけを返しました(x-nextjs-* は公開仕様ではないので、ヘッダの意味は断定しません。値が違うルートは同じ確認で代表できないという判断材料としてのみ使います)。
ここが要点です。「トップが 500」と「404 が 500」は 2 つの故障ではなく、1 つの故障の 2 つの見え方でした。 上流は 2 つ目しか見ていなかったので、そこから別の原因(404 ハンドラの欠陥)を作り出してしまいました。
2-2. 下流「実行環境のアダプタが新しい版を阻んでいる」→ 誤り
pin を上げられない理由として、下流は実行環境用アダプタの peer dependency を疑いました。上流が公開レジストリを引いたところ、逆でした。
# アダプタの、当時インストールされていたバージョンの peerDependencies を
# 公開 npm レジストリから引く(2026-09-10 取得)。<pkg> / <version> は自分の値に置き換える
curl -s https://registry.npmjs.org/<pkg>/<version> | jq -c .peerDependencies
# => 返ってきたのは、こういう形の「離れた 2 区間の和」だった:
# {"<フレームワーク>": ">=A.b.c <D || >=D.e.f", "<CLI>": "^G.h.i"}
引いてみると、上げようとしていた版は後ろの節に入っていました。 むしろどちらの節にも入っていなかったのは、当時 pin していた版のほうでした。
この 2 つは独立した 2 つの測定ではありません。 レジストリから引いたのは 1 本の文字列で、「上げ先は範囲内」も「pin 先は範囲外」も、その 1 本を semver の範囲式として評価した同じ 1 回の観測から導かれる結論です。数を 2 に見せないためにここを明示しておきます。
具体的なバージョン番号とパッケージ名は本記事に書きません。 本記事が載っているのは、いま話題にしている公開先そのものです。稼働中の依存バージョンを、外形監視が動いていないという記述と並べて出すことになるため、値のほうを落としました(第 5 節で改めて書きます)。持ち帰ってほしいのは値ではなく、peerDependencies を引いて範囲式を semver として評価するという操作のほうです。
言えるのはここまでです。/ が 500 になる直接の機序は、実行時ログで捕捉されていません(第 2-1 節に書いた機序は先行事象からの推定です)。切り分けとしては、下流が preview で 2 つのバージョンの両方に再現を確認しており、「新しい版に上げたこと」ではなく「pin を外したこと自体」が引き金だと判定しています。レジストリが示したのは「アダプタが新しい版を阻んでいるという筋書きはこのデータでは支持されない」ことだけです。一方、同じ peer 定義のもう 1 つ(CLI 側)は満たされていませんでした。 下流の lock file が解決していた版が、要求されたキャレット範囲の下限に届いていなかったためです。ここも数字なしで持ち帰れる形にしておくと、宣言側(package.json)の範囲は要求下限を許すのに、lock file が解決している実体は届いていないという食い違いでした。宣言だけ見ると充足しているように見えるので、lock file を読まないとこの判定はできません。 上げるのが 1 手ではなく CLI を含む連鎖だった、という以上の主張はしません。
下流はこの指摘を受けて、「私が示唆したのは誤りでした」と自分の推測を撤回しています。撤回が成立したのは、上流が「引いた結果の文字列」と「どこから引いたか」を一緒に渡したからです。「アダプタのせいじゃないと思う」だけなら撤回はできません。
2-3. 上流「4 日間、誰も気づかなかった」→ 成立しない
上流は「開発ブランチで pin を前進させてから 4 日間、壊れていることが検知されなかった」と書きました。下流が本番リリースの履歴を出して訂正しました。
pin が前進したのは開発ブランチ上で、本番に到達したのは 1 回だけです。
| 時刻(JST、2026-09-10 に下流の履歴から取得) | 何が起きたか | どこで |
|---|---|---|
| 2026-08-13 | 直前の本番リリース | 本番 |
| 2026-09-06 04:36 | pin を前進(1 段目) | 開発ブランチ |
| 2026-09-10 07:13 | pin をさらに前進(2 段目) | 開発ブランチ |
| 2026-09-10 07:24 | リリース。ここで初めて新しい pin が本番へ出た | 本番 |
| 2026-09-10 09:02 | 切り戻しリリース | 本番 |
つまり 2026-09-06 から 09-10 のあいだ、本番は前の pin で動いていました。 これは各時刻に / を測った実測ではなく、「本番への反映はリリース操作でしか起きない」ことと上の履歴からの演繹です。検知すべき対象が本番に存在しなかった 4 日間を「検知されなかった 4 日間」と呼ぶことはできません。
「1 時間 38 分」は測定値ではありません。 これは本番リリースと切り戻しリリースの記録時刻の差で、履歴から引き算で決まる値です。実際に 500 を返していた区間は、壊れた版が公開に反映された時刻から、切り戻しが反映された時刻まで。どちらもリリース記録より後ろにずれるので、この差は近似値であって上限ではありません(両側のずれ幅を測っていないので、どちらに何分ぶれるかも言えません)。下流は当時「約 1 時間」と報告していますが、この値はリリースノートにも台帳にも残っていない口頭の報告で、私は追試できていません。確かなのは、この事象が日単位ではなく時間単位だったことだけです。
なお、上流の元の懸念が丸ごと消えたわけではありません。開発ブランチには 4 日間、壊れた pin が載っていて、プレビュー環境も出ていました。 上流が言いたかった「4 日間あったのに分からなかった」は、本番の検知の話としては成立しませんが、出す前の確認の話としては成立します。 訂正されたのは主張の範囲であって、問題意識そのものではありません。ここを潰さないのが、訂正を受け入れる側の仕事です。
2-4. 両者「監視対象に / が入っていなかったから検知できなかった」→ 誤り
これは 2 者が同じ誤りに独立に到達した唯一の項目です。「トップだけが落ちた」「検知できなかった」の 2 つを並べれば、「トップを見ていなかったのだろう」は自然な推論です。両方がそう書きました。
訂正したのは上流のレビュアーで、下流の一次資料(外形検査の CI 設定と、それとは別の外形検査スクリプト)を読んで発見しました。 そして読んでみると、そもそも外形検査は 1 つではなく 2 つありました。
検査 A: CI が自動で走らせるはずだった側
デプロイ後に走ることになっている CI ジョブは、HTTP リクエストを直接投げる形で 2 つの URL だけを見ています。トップページ / と、sitemap です。 つまり / は「入っていなかった」どころか、このジョブが見ている唯一のページでした。
問題は起動経路でした。ワークフローのヘッダに、当時の状態がそのまま書いてあります。起動経路は 3 系統ありましたが、自動で走るものは 1 つも残っていませんでした。
| 起動経路 | 2026-09-10 時点の状態 |
|---|---|
| デプロイ完了イベントで自動起動 | 不発。 配信基盤がそのイベント自体を作らない構成で、導入直後にその旨がヘッダへ追記されている |
| 定期実行 | 停止中。 経路上の bot 対策が素の HTTP クライアントを 403 にするため、コメントアウトされていた。復活条件もヘッダに書かれている |
| 手動起動 | これだけが稼働(=誰かが押さないと走らない) |
検査 B: 対象が充実しているほうの側
もう 1 つ、リポジトリにはもっと網羅的な外形検査スクリプトがあります。こちらの検査対象は 16 件で、["/", 200, "top"] が配列の先頭です(固定 5 件に加えて、公開記事 3 本・未公開記事 4 本・削除済み 2 本・OG 画像 2 件を記事の在庫から組み立てます)。
# 外形検査スクリプトの assertion 配列を ref 指定で読む
git show <下流の ref>:<外形検査スクリプト> | sed -n '/const assertions/,/];/p'
# => ["/", 200, "top"] が配列の 1 番目。以下 一覧 / タグ / sitemap インデックス / 記事 sitemap が続く
しかも約 5 か月前からです。追加された時点を履歴で確定できます。
git log --diff-filter=A --format='%h %ad %s' --date=short <下流の ref> -- <外形検査スクリプト>
# => 2026-04-11(この最初のコミットの時点で ["/", 200, "top"] が入っている)
対象の設計は、むしろ良くできていました。トップ、一覧、タグ、sitemap インデックス、記事 sitemap、公開記事 3 本の 200、未公開記事 4 本の 404、削除済み記事 2 本の 404、OG 画像の 200 と 404。「404 を返すべき URL を、返すべき理由の種類ごとに分けて」検査しています。
ところがこの検査 B は、外形疎通としては CI から一度も呼ばれていません。 スクリプト本体は CI に載っていますが、載っているのは合成フィクスチャと HTTP のモックを使った単体テストのほうで、本番のオリジンへは 1 度もリクエストを出しません。実サイトを叩く起動経路は、手元から手で叩く 1 本だけです。さらに、この時点では叩いても走りません。 未公開記事を 4 件以上必要とする前提を持っていて、下流の記事在庫が未公開 0 件になっていたためです。下流自身がその旨を「本番 smoke が回らない状態は別途修正が要る」と記録しています。
3 段構えで、どれも対象の問題ではない
並べるとこうなります。
| 検査 A(CI 側) | 検査 B(網羅側) | |
|---|---|---|
/ を見ているか | 見ている(見ているのはほぼ / だけ) | 見ている(配列の先頭) |
| いつから | — | 2026-04-11(約 5 か月前) |
| 実サイトに対して自動で走るか | 走らない(3 経路とも死んでいる) | 走らない(CI に載っているのはモックを使った単体テストだけ) |
| 手で叩けば走るか | 走る | 走らない(フィクスチャ前提を満たせない) |
「監視対象に / が無かった」は、2 つの検査のどちらについても誤りでした。真因は 3 つとも起動側にあります。
そしてこの 3 行は、スクリプト 1 本を読むだけでは出ません。 CI 設定とその冒頭コメント、パッケージのスクリプト定義、記事在庫の状態、そして下流自身のリリース記録——全部が下流にしかありません。
なお、この「置いてあるのに発火しない」形そのものは、本ブログの 効いていないガードを見つける7つの型 で 7 種類に分類しました。本記事が足しているのは、その分類を当てはめるための材料が、丸ごと相手側にしか無かったという点です。
上流は「対象に足せ」という処方を書きかけていました。もし突き合わせをせずに公開していたら、同じ構成の読者は監視対象を増やして、同じように検知できないままになります。誤った処方は、無処方より害があります。
3. なぜ独立した観測の突き合わせで誤診が落ちるのか
4 件を並べると、「コミュニケーションが大事」よりずっと具体的な構造が出てきます。
3-1. 測っている対象が違う
上流が持っているのはサイトの外形です。HTTP ステータス、応答ヘッダ、本文。下流が持っているのはリポジトリの設定と履歴です。pin の値、リリースの時刻、検査の起動条件。
この 2 つは、同じ障害についての別の断面です。だから片方の断面だけで因果を書くと、必ずどこかで断面の外に出ます。2-1 は外形だけで内部機序を推定して外し、2-4 は「検知できなかった」という外形の事実から内部の配線を推定して外しました。
重要なのは、同じ結論に別の経路で到達したときだけ確度が上がることです。同じ経路で 2 回到達しても、それは 1 回です。2-4 で 2 者が独立に同じ誤りへ到達したのは、両者ともこのときだけ同じ経路(「トップが落ちた」+「検知できなかった」という外形の 2 事実)を使ったからです。独立な観測者でも、入力が同じなら誤りは相関します。
3-2. 測った時刻が違う
2-1 は障害の最中の観測で、2-3 は復旧後に履歴を読んだ観測です。同じ URL が、時刻によって 500 と 404 を返しました。 どちらの測定も正しく、結論だけが間違っていました。
ここから出る規則は 1 つです。時点を書かない観測は、突き合わせられません。 「404 が 500 を返す」には反証も追認もできませんが、「2026-09-10 の本番リリース後・切り戻し前に測って 404 が 500 を返した」なら、復旧後に測り直した値と並べて「時刻が違う」と言えます。
同じ理由で、引き算で決まる値と実測値を区別して書く必要があります。2-3 の「1 時間 38 分」は履歴から必然的に決まる値で、/ の応答を測って得た値ではありません。区別せずに並べると、測った値の数を水増しすることになります。
3-3. どちらも「自分の側の一次資料」しか持っていない
4 件のうち 2 件(2-3 と 2-4)は、相手側のリポジトリを読んで初めて出ました。 もう 1 件(2-2)は、どちら側にも無い外部の一次ソースで出ました。
| # | 誰の主張だったか | 誰が訂正したか | 訂正に必要だった一次資料 | どちら側にあるか |
|---|---|---|---|---|
| 2-1 | 上流 | 上流(復旧後に測り直し) | 復旧後の HTTP 応答 | 上流が取得可能 |
| 2-2 | 下流 | 上流 | 公開 npm レジストリの peer 定義 | どちらでもない外部 |
| 2-3 | 上流 | 下流 | 本番リリースの履歴 | 下流 |
| 2-4 | 両者 | 上流のレビュー工程 | 外形検査の CI 設定とそのヘッダ / 別系統の外形検査スクリプト / 記事在庫の状態 | 下流 |
この表がいちばん言いたいことです。自分の側の資料を深く読んでも、2-3 と 2-4 は出ません。 深さの問題ではなく、そこに無いという問題です。そして 2-2 は「どちら側にも無い外部の一次ソース」で、これは両者が同じくらい引き忘れる種類のものでした。
裏返すと、突き合わせの価値は「賢い人が 2 人いること」ではなく、アクセスできる一次資料の集合が違うことにあります。同じリポジトリしか読めない 2 人を並べても、2-3 と 2-4 は落ちません。
本ブログでは、同一リポジトリの中で「宣言(規約に書いてあること)」と「実効(設定が実際にしていること)」を突き合わせる話を AIガバナンスが効かない4類型と検証手順 で、独立に集めた 2 系統のデータを突き合わせる話を AI-BOMで何がコードを書いたか可視化する で扱いました。どちらも 1 つのリポジトリの内側で完結します。 本記事が足しているのは、突き合わせる 2 系統がそもそも別のリポジトリにあり、片方からもう片方が読めない場合の話です。
4. 訂正が成立する条件
指摘したのに直らない、という経験は誰にでもあります。上の 4 件で実際に訂正が成立した条件は 2 つでした。
4-1. 実測値と測定手段を添えて渡す
2-2 で下流が推測を撤回できたのは、上流がレジストリから引いた文字列そのものとどこから引いたかを渡したからです。「アダプタのせいではないと思う」だけでは撤回できません。撤回とは、相手が自分の手で確かめて負けを認める行為なので、確かめる手段が同梱されていないと物理的に成立しません。
渡す単位は、最低でもこの 4 つです。
| 添えるもの | 無いとどうなるか |
|---|---|
| 実測値そのもの(丸めない) | 相手が同じ値を再現したのか別の値なのか判定できない |
| 測定手段(コマンド / 経路) | 相手が測り直せない。測り直せない主張は反証されずに生き残る |
| 測定時刻 | 2-1 のように、時刻違いを「恒久的欠陥」と読み違える |
| 参照した ref(SHA / タグ / URL) | 作業ツリーは常に stale。読んだものが特定できない |
4 つ目は実際に効きました。本記事の確認はすべて git show <ref>:<path> の形で行っています。作業ツリーを読んで数値を確定させると、相手が同じものを読んだ保証が消えます。
4-2. 相手の主張を再現してから否定する
2-4 で「監視対象に / が無い」を否定するとき、先にその主張が成り立つ世界を再現しました。 つまり「対象に / が無ければ確かに検知できない」を認めたうえで、実際の対象一覧を出して「しかし入っている」と示しています。
これは礼儀の話ではありません。再現しないと、相手が本当は何を見て言ったのかが分からないという実務上の問題です。2-3 がいい例で、上流の「4 日間」は本番の話としては誤りでしたが、開発ブランチの話としては正しい観測を含んでいました。再現せずに「4 日間は誤り」とだけ返していたら、正しい部分まで一緒に捨てていました。
否定するときの形はこうなります。
- 相手の主張が成立する条件を明示する(「対象に
/が無ければ検知できない」) - その条件を実際に測る(対象一覧を ref 付きで出す)
- 条件が成立していないことを示す(
/は先頭にある) - 相手の観測のうち、生き残る部分を明示する(「検知できなかったのは事実。原因が対象ではなく起動経路」)
4 が抜けると、訂正が相手の観測ごと捨てる形になり、次から観測が出てこなくなります。
5. この記録の限界
- 1 事象・2 者・4 件です。 発生率ではありません。私は「相手の結論に誤りがあるか」という問いを持って両側の資料を読んでおり、探せば見つかる方向のバイアスがかかっています。主張できるのは「起きた」と「起き方に構造がある」までで、「よく起きる」ではありません。
- n=1 とも書きません。 訂正は 4 件、方向は上流→下流が 2 件(うち 1 件は、上流自身が同じ誤りを書いていたので自己訂正でもあります)・下流→上流が 1 件・上流の自己訂正が 1 件で、事象は 1 件・リポジトリは 2 つです。「1 件の話」と丸めると、2-3 と 2-4 が別の経路で出たという中心的な事実が消えます。弱める方向に丸めるのも誤りです。
- 2 者が独立だったのは偶然の産物です。 役割分担として設計されたものではなく、たまたま担当が分かれていました。この構造を意図的に作るコストの評価は本記事では扱っていません。
- 下流の一次資料は private です。 読者は同じ資料を追試できません。本記事が示せるのは構造と手順であって、個々の値の追試可能性ではありません。追試できるのは上流側の値(
origin/main=a184564)だけです。 - 第 2-2 節のバージョン番号とパッケージ名は、意図的に落としました。 第 4-1 節で「実測値そのものを丸めずに渡せ」と書いておきながら、ここだけそれを破っています。理由を書きます。この記事が載るのは、いま話題にしている公開先そのものです。 稼働中の依存バージョンを、第 2-4 節の「外形監視が 3 経路とも動いていない」という記述と同じ記事の中に並べると、読者は「このサイトはいま、この版で動いていて、しかも誰も外から見ていない」を読み取れてしまいます。追試可能性と、稼働中の構成を推定させないことを天秤にかけて、後者を採りました。渡す相手が限られている場面(相手のリポジトリに書く、社内のチャンネルで渡す)では、値を落とす理由はありません。落とすかどうかは、渡す先が誰かで決まります。
- 公開してよいものの禁止リストは、識別子だけでは足りませんでした。 初稿の禁止リストは SHA・PR 番号・タグ・ホスト名・ワークフロー名・内部パスという識別子の列挙で、そこは実際に 1 件も漏れていません。それでも稼働中の依存バージョンは素通りしました。識別子ではないが、他の記述と組み合わせると危ない値という枠が抜けていたためです。独立レビューの指摘で気づき、禁止リストに 1 項目足しました。
- 2-1 の機序は先行事象からの推定です。 今回の障害で実行時ログを捕捉していません。「ワーカー初期化時の例外」は同一シグネチャの先行事象で特定された原因を再適用したもので、今回それを実捕捉して確認したわけではありません。
- 時刻はすべて JST、下流の履歴から 2026-09-10 に取得した値です。
- 本記事の観測は、上流の失敗台帳へ
L-0057として起票しました。上流のリポジトリ内の記録なので、読者が参照できる先ではありません。
まとめ
- 同じ障害を独立に測っていた 2 者が、互いの結論を 4 件訂正しました。うち 3 件は、片方だけなら通っていました(残る 1 件は上流が自分で測り直して訂正できたものです)。
- 誤診が落ちたのは話し合ったからではなく、測っている対象・測った時刻・持っている一次資料が違ったからです。同じ入力を使った 2-4 では、独立な 2 者が同じ誤りへ到達しています。
- 4 件のうち 2 件は、相手側のリポジトリを読んで初めて出ました。 自分の側を深く読んでも出ない種類の誤りです。
- 訂正を成立させるのは、実測値・測定手段・測定時刻・参照 ref を添えて渡すことと、相手の主張を再現してから否定し、生き残る部分を明示することです。
- 明日からできる操作は 1 つ。次に誰かへ観測を渡すとき、「いつ」「どうやって」測ったかを本文に書く。 書かない観測は、突き合わせの対象になれません。
FAQ
レビュアーを増やせば同じ効果が得られますか
得られません。本記事の 4 件のうち 2 件は、相手側のリポジトリの一次資料を読んで初めて出ました。同じ資料しか読めないレビュアーを何人並べても、この 2 件は出ません。効いているのは人数ではなく、アクセスできる一次資料の集合が違うことです。実際、2 者が同じ 2 つの外形事実だけを入力にした 2-4 では、独立な 2 者が同じ誤りへ到達しています。
「404 が 500 を返す」という観測自体は間違っていたのですか
観測は正しく、結論が間違っていました。障害の最中には実際にそう返っていました。誤りは「だからサイト側に恒久的な欠陥がある」という部分です。復旧後に同じ URL を測ると 404 が返ります。観測に時点が付いていなかったことが、時刻依存の値を恒久的な性質として読ませました。
相手の主張を否定するとき、何を書けば伝わりますか
4 つです。(1) 相手の主張が成立する条件、(2) その条件を実際に測った結果と測定手段、(3) 条件が成立していないこと、(4) 相手の観測のうち生き残る部分。4 が抜けると、訂正が観測ごと捨てる形になり、次から観測が出てこなくなります。本記事の 2-3 では「4 日間」は本番の話としては成立しませんが、開発ブランチの話としては成立する部分が残りました。
突き合わせに時間をかける価値はいつありますか
本記事で価値が出たのは、主張が「相手側の設定や履歴」に依存しているときでした。2-3 と 2-4 がそれです。2-2 は少し違って、どちら側にも無い外部の一次ソースを両者が同じくらい引き忘れていたケースでした。逆に、自分の側で完結する主張(2-1 の測り直し)は自分で訂正できています。目安として、「相手のリポジトリを読まないと確かめられない」と書いた瞬間が、突き合わせを起こすべき合図です。コスト評価は事象 1 件分しか持っていないので、常に見合うとは主張しません。
参照した資料が private だと、記事として成立しますか
値の追試可能性は失われます。本記事が公開しているのは挙動と手順の構造だけで、SHA・PR 番号・タグ・ホスト名・ワークフロー名・内部パス、そして稼働中の依存バージョンは書いていません。最後の 1 つは private だからではなく、この記事が載っているのがその公開先自身だから落としました(第 5 節)。結果として、読者が追試できるのは上流側の基準時点(origin/main = a184564)だけです。第 2-2 節のレジストリ照会も、手順は再現できますが値は再現できません。追試できない部分は追試できないと書く、というのが本記事の取った処理です。
References
- npm Registry API — package metadata(
peerDependenciesの取得元) - node-semver — Ranges(
||で連結した離れた区間の評価規則) - MDN — HTTP 500 Internal Server Error
- MDN — HTTP 404 Not Found
- Next.js —
not-found.js(ルート直下の not-found ハンドラ) - GitHub Actions — Events that trigger workflows(
schedule/deployment_status/workflow_dispatch) - Google SRE Workbook — Postmortem Culture: Learning from Failure
- git —
git show <rev>:<path>(作業ツリーではなく ref の内容を読む)
