Cloudflare Pagesの失敗が届かなかった — デプロイ経路を出口で止める

  • #デプロイ
  • #Cloudflare Pages
  • #CI
  • #ゲート

今回やったこと

本番ビルドが失敗しても通知が読まれず、別の経路では検査完了を待たずに変更が main に入る状態を見直した。対象は記事の内容ではなく、記事を本番へ運ぶデプロイ経路である。失敗を検知するだけでなく、壊れた入力を出口へ進ませず、必要な通知を読まれる場所へ届けることを目標にした。

きっかけは、ビルドの標準出力と標準エラーを捨てていたことだった。site_deploy.sh は以前、ビルドを実行してもログを残さず、失敗した理由が後から追えなかった。通知が存在していても、失敗理由が見えなければ復旧の入口にならない。通知列が騒がしい場合は、正しい通知でも埋もれる。

検査をコミットの前へ置く

記事を commit する前に astro sync を通すゲートを追加した。これは手書きの validator で似たルールを再現するのではなく、Astro のコンテンツスキーマそのものを実行する方式である。

python3 scripts/astro_content_gate.py --site <site-name>

このスクリプトは、スキーマ違反を終了コード1、検査できない状態を終了コード2として扱う。node_modules が無いときに黙って検査を飛ばさない点も重要だ。検査をしなかったのに緑を返すと、ゲートが存在するだけで機能していない状態になる。必要な場合だけ --allow-missing-deps を呼び出し側が明示する。

手書きミラーでは、スキーマにある数値範囲や整数制約などを写し忘れる可能性がある。スキーマ側を直接呼べば、検査ルールの追加時に別の validator へ同じ変更を転記する必要がない。ゲートの担当範囲はスキーマ適合性に限り、出典や記事のトーンまで代替しない。

配信失敗を捨てずに知らせる

site_deploy.sh はビルドが成功したときは従来どおり静かにし、失敗時だけログの末尾を launchd のログと Discord へ渡す。全文はサイトごとの build log に残す。これで「失敗した」という状態と「なぜ失敗したか」を分けて確認できる。

通知側では、配信失敗を action として扱い、理由コードを blocked に固定する設計へ寄せた。さらに同じ案件を短時間に繰り返さない静穏化と、必要な案件を後から追える outbox を送信の合流点へ集約した。通知の抑制は記録の代わりではない。記録してから抑制することで、何が黙っていたかを監査できる。

この変更で、赤い検査を置くだけの設計から、次のような流れになった。

段階役割失敗時の扱い
astro syncコンテンツスキーマを検査commit の前で停止
ビルド配信物を生成出力をログへ残す
デプロイ本番へ配る失敗を action 通知へ送る
outbox対応案件を保持同じ案件を束ねる

うまくいかない条件も残す

CI の終了を待たずにマージする経路は、検査が遅いことを理由に安全性を失う。逆に、すべてをフルビルドで確認すると、失敗地点が遠くなりフィードバックも遅くなる。そこでスキーマ関門は astro sync に絞り、配信前のビルド失敗は別のログと通知で扱う役割分担にした。

この構成でも、通知先の設定が無い、実行機の依存が壊れている、といった問題は別途点検が要る。通知スクリプト自身が依存不足で落ちると、異常を知らせる側が黙るためだ。実装では標準ライブラリを優先し、通知経路の依存を小さくしている。

参照した実装は scripts/astro_content_gate.pyscripts/site_deploy.shscripts/notify_discord.py である。デプロイ失敗の可視化と配信後チェックの状態待ちに関する変更を整理した。

環境・バージョン: リポジトリの実装記録にある Node.js v22.22.2 を基準とし、ゲートは Python から npx astro sync を起動する。実行機の依存が一致しない場合は、検査を省略せず停止する。

更新履歴

  • 2026-09-02: 初稿。リポジトリ内の実装とテスト記録をもとに整理。

訂正履歴

(なし)