マージするたびにリリースする
10月25日、私たちのブラウザー運転シミュレーター、Seat Timeの変更が本番に届きませんでした。本番とは、公開中のゲームです。Seat Timeは、私たちが人工知能(AI)のコーディングエージェントと作っているゲームです。変更を統合する操作をマージと呼びます。共有のリリースブランチmainへのマージで公開するはずでした。しかし、以前のビルド、つまりコンパイル済みのゲームが動き続けていました。障害記録には「誰にも通知されず、ビルドが赤くなっただけ」と残っています。
その頃には、ラップ記録、ゴースト、競技ラウンドがありました。数日おきに物理を変えていたので、公開中のビルドを知る必要がありました。私たちは、機能のスイッチを改めて切り替えず、マージで作業を終えたかったのです。その午後、マージからブラウザーに届くまでの間に、まだ問題が残っていると分かりました。
前回:名前に収まらなくなった。
リリース用のフラグを取り除きました
10月18日、リリース用のフラグを23個削除しました。これらは、マージ済みの機能を別途有効にするためのスイッチでした。mainにマージしたら、それを公開する方針です。お金に関わる機能は専用のスイッチを残し、初期状態では無効にしました。
この方針で、生成値もリリースの一部になりました。ビルドには、サーバー上の再生処理を識別する整合性ダイジェストを埋め込みます。この処理は、ゲーム内のアルバイト活動で記録した走行を再生します。ダイジェストはいわばコードの指紋で、受け入れ済みの値と違えば処理の起動を拒否します。コードをマージするときは、生成物との一致も確認する必要があります。
すべてのラップに物理バージョンを付けました
ラップを物理バージョンで分ける仕組みは、10月12日にバージョン2から始めていました。ラップ、ゴースト、ランキングの各記録に識別子を付けました。古い記録は別のグループに残します。異なる物理で走ったラップ同士は競わせません。
標準状態のラップタイムが変わる変更では、物理バージョンを上げるルールです。そうしなければ、速いラップが運転の上達ではなく車の変更によるものかもしれません。条件が同じだったことにせず、過去の結果を保存できます。物理のリリースでは、新しいコード以外にも責任を持つことになりました。
公開中のビルドに答えてもらうページを作りました
エージェントに/versionを追加してもらい、実行中のビルドを識別できるようにしました。識別情報が取れない場合は、代わりの値を作らず利用不可の応答を返します。マージの成功だけでは、本番の状態を証明できません。動いているサイト自身に聞けるようにしました。
基本操作を短くたどる、自動のスモーク確認も追加しました。以前、コンパイルできた変更が3つのボタンを壊していました。設定は開かず、ランキングは「読み込み中…」のままでした。ゴーストとのレースを選ぶと設定が開きました。パッチの断片が違うメソッドに入ったためです。これが、公開確認でも実際の操作を試す理由になりました。
古いビルドが新しいビルドを上書きしました
ビルドの完了順は、コードの新しさと一致しませんでした。実際に、古いビルドが新しいものを置き換えました。本番への経路が複数あることも、状況を分かりにくくしていました。そこで公開処理は、最後のアップロードを最新とみなさず、公開の前後でコードの順序を確認します。
本番のスモーク確認は、誤って失敗と判定することもありました。自動レンダラーのシミュレーションは実時間より遅く進み、以前の確認は一度も通っていませんでした。時計の経過時間ではなく、シミュレーションの進行で判定するように変えました。本番で2回通り、その後の待機処理もフレーム数だけでなくゲームの進捗を見るようになりました。
公開処理を一本化し、公開後も確認します
公開処理は、mainをビルドするCloudflare Workers Buildsの1つにまとめました。候補をトラフィックなしで準備し、起動時のプロセッサー時間を確認します。私たちが選んだ上限は400 msです。遅い候補や測定値のない候補は、データベース変更の前に止めます。
次に順序を確認し、データベースを移行し、準備状態を確かめて候補を公開します。その後、公開中の識別情報、Web応答、ブラウザーでの操作を確認します。失敗した場合、その候補がまだ公開中なら以前の版に戻します。新しいビルドがすでに公開されていれば、古い公開処理はそれを取り消さず退きます。
ブラウザーでの操作確認より先に公開するため、これは復旧の仕組みでもあります。「アップロード済み」を「受け入れ済み」とは扱いません。実行中のゲームまで届き、操作できることを確認します。公開が拒否されたり、以前の版に戻されたり、新しいビルドに置き換わったりすることもあります。それでも、コードは次の成功したビルドに含まれます。
マージしたのに公開されませんでした
10月25日、リポジトリの複製に約8.5分かかりました。それまでは約3分でした。候補の準備が終わったのは約12.5分の時点で、公開処理は期限まで392秒と報告しました。開始条件は10分の余裕だったため、公開を拒否しました。
ビルドには、Workers Buildsの20分のタイムアウトがあります。公開処理は余裕を残すため、内部の期限を19分に設定しています。記録された各段階の時間は概数で、392秒は内部期限までの報告値です。丸めた準備時間を20分から引いた残り時間ではありません。
拒否によって確認処理の途中打ち切りは防げましたが、以前のビルドが公開されたままでした。失敗はビルド結果に出ただけで、公開漏れに気づく通知にはなりませんでした。新しい確認コマンドが測定したとき、本番はmainから30分遅れていました。リリース方針を実際の公開と結び付けるには、別の証拠が必要でした。
遅れを見えるようにしましたが、復旧には限界が残りました
観測した所要時間から、公開開始に必要な余裕を6分に減らしました。ゲスト操作の確認は、準備を含め1.9〜3.1分でした。時間切れを理由に合格させず、ブラウザー確認は必須のままにしました。公開への切り替えが遅ければ、巻き戻しになる可能性は残ります。
エージェントは、プッシュで始まったビルドに1回だけ再試行する仕組みを加えました。プッシュとは、リポジトリに変更を送る操作です。再試行の繰り返しや既存ビルドの重複は認めません。新しい候補が準備済みの場合も再試行しません。しかし10月28日、自動再試行は権限不足で拒否されました。記録上の復旧はダッシュボードから行っています。無人で復旧できた証拠にはなりません。
新しいcheck-liveは、mainと実行中のビルドを比べ、25分を超える遅れを警告します。これは私たちが選んだしきい値で、配信時間の保証ではありません。11月6日の確認では、定期実行の指示はありましたが、リポジトリ内の設定は見つかりませんでした。自動監視と呼ぶ前に、定期実行の確認が必要です。
今は、マージ済み、準備済み、公開済みを分けて報告します。ローカル確認はマージ前に終え、公開を確かめずにまとめてマージしない方針です。「公開されていますか」には、実行中のビルドから直接答えられます。「マージしました」だけでは答えになりません。
翌週、リリースの問題はテスト環境にも及びました
翌週から11月2日にかけて、物理バージョンはv8からv17へ進みました。そのたびに指紋、基準走行、メダル基準値、生成記録を更新しました。競技期間の終了を待ったリリースもあります。11月1日には、準備できた変更から継続的に公開する方針を選びました。
物理の変更は、記録上の結果も動かしました。v16では、自動スモークのラップが133.425秒から132.469秒になりました。Jump Lineの記録されたスコアはv15の銀メダルに当たる621から、銅メダルの594に変わりました。v17ではメダル基準値は変わりませんでした。どちらのリリース記録にも、マージ前のG923やDualSenseでの走行はありません。これらの確認から、各ビルドのハードウェアでの運転感覚は語れません。
テスト環境自体にも修正が必要でした。ローカルの継続的インテグレーションは、変更を統合する際にテストする仕組みです。そこからSeat Timeのテストファイル710個のうち575個が漏れていました。その実行処理からの漏れであり、誰も一度も実行しなかったという意味ではありません。未分類のテストを拾うグループを追加しました。各ファイルが必ず1グループに属する確認も加えました。
さらに、プロジェクトのフォルダー名にある空白がVitest 4.1.10のブラウザーモックを壊しました。モックは、テストで使う代用品です。空白を含むディレクトリへの上流修正には、検索に空白が必要な箇所で%20が残る問題が説明されています。私たちは、使用バージョンを固定した依存ツールにパッチを当てました。11月6日時点で上流にはマージ済みでした。しかし、エラーの記録を読むv4.1.12の解析コードには、そのデコード変更がありませんでした。
生成値を違うコード状態で作り直したことも、次の公開問題につながりました。そこで、最終的にマージされたコード上で再生成するルールにしました。リリース名も、実際に公開する物理バージョンに合わせて1つにします。コード、証拠、報告する名前をそろえるためのルールです。
次は、ビルドの中にある数字です
マージと公開を区別できるようになりましたが、監視の定期実行はまだ確認が必要です。再試行の権限不足もあり、自動復旧が完成したとは言えません。私たちの解釈では、この時期の保護策にはそれぞれ原因となった障害があります。すべてのラップに物理バージョンが必要なら、その物理の数字にも出典が必要です。
次回:すべての数字に出典を付ける。
注記と出典
- 資料に基づく事実 — 私たちのリリース記録: 10月18日に23個のフラグを削除し、金銭関連のスイッチは初期状態で無効のまま残しました。コードの指紋、
/version、壊れた3つの操作、古いビルドによる置き換え、公開処理の一本化は実装の記録です。シミュレーション時間によるスモークは2回通りましたが、信頼性の割合を示す値ではありません。 - 設計上の選択値 — 私たちのルール: 物理の識別は10月12日にバージョン2から始めました。古いラップ、ゴースト、ランキングは分けます。起動上限は400 ms、内部期限は19分です。必要な余裕は10分から6分へ変更し、条件付きの再試行は1回、遅延警告は25分超としました。
- 測定値 — 10月25日の障害: 複製は約3分から8.5分へ、準備完了は約12.5分、残り時間の報告は392秒でした。測定時の本番は30分遅れ、ゲスト操作の確認は1.9〜3.1分でした。過去の測定であり、新たなベンチマークではありません。
- 資料に基づく事実 — プラットフォーム上限: CloudflareのWorkers Buildsの制限と料金には、20分のタイムアウトが記載されています。2026年11月6日に読みました。それ以外の上記しきい値は私たちの設定です。
- 資料に基づく事実 — 残る運用上の制約: 10月28日の記録では、再試行は権限不足となり、ダッシュボードで復旧しました。11月6日の確認では、
check-liveの定期実行設定をリポジトリ内に見つけられませんでした。リポジトリ外で何が動いているかを断定するものではありません。 - 資料に基づく事実/設計上の選択値 — その後: v8〜v17、競技期間による待機、11月1日の継続的な公開方針、名前の統一、最終マージ後の再生成は、私たちのリリース記録に基づきます。
- 測定値/ハードウェアでは未走行: v16のスモークラップは133.425秒から132.469秒へ、Jump Lineの記録されたスコアは621から594へ、銀から銅へ変わりました。v17でメダル基準値は変わりませんでした。記録にはマージ前のG923やDualSenseでの走行がなく、これらの確認から運転感覚は語れません。
- 測定値/資料に基づく事実 — テスト環境: ローカル確認から710個中575個のテストが漏れていました。モックの障害はVitest 4.1.10で記録されています。空白の修正は2026年10月9日にマージされました。11月6日にv4.1.11の記録、v4.1.12の記録、v4.1.12の解析コードを読みました。その解析コードにはデコード修正がなく、ローカルパッチを外せるとは主張していません。
- 私たちの解釈: これらの失敗により、リリースの識別情報と証拠が公開作業の一部になりました。AIコーディングエージェントが実装を手伝い、公開の判断は私たちが行いました。この記事の執筆と翻訳にもAIを使用しています。製品名やツール名は説明のためであり、推奨を意味しません。