本文へスキップ
開発日記読了 約9分

git pushしたのに本番に反映されない——未コミット・未push・未デプロイを切り分ける

「本番に反映されない」という一言には、直し方がまったく違う3つの状態が混ざっています。①未コミット(変更が手元にしか無い)②未push(GitHubに届いていない)③未デプロイ(pushしたのに、出す係が動いていない)の3つです。私たちはこのうち③を疑わなかったせいで、自社プロダクトの変更8件を11日間、本番に出さないまま放置しました。この記事では、その誤診の記録と、gitコマンドを使わずに3状態を毎日自動で切り分けている検査のしくみを公開します。

push済みなのに本番が古い — 未コミット・未push・未デプロイを切り分ける
未コミット・未push・未デプロイの3状態と、それぞれの直し方を並べた図
3つの状態は、見る場所も直し方も違います

「本番に反映されない」は3つの別々の状態です

まず言葉を分けます。この3つは症状が同じ(本番が古い)だけで、原因も直し方も別物です。

状態何が起きているか確かめ方直し方
①未コミット変更が作業ツリー(手元の編集中の状態)に残っている変更の一覧が空かどうかコミットする
②未pushローカルの枝先と、GitHub側の枝先が違う.git/refs/heads/main と .git/refs/remotes/origin/main の中身を比べるpushする
③未デプロイ両方そろっているのに、本番だけ古い本番のHTTPヘッダで「いつの版か」を見るデプロイ経路を疑う

大事なのは、①と②は手元だけで分かるのに、③は本番側を見ないと絶対に分からないという非対称です。手元を何度見直しても③は見つかりません。私たちが落ちたのは、まさにこの穴でした。

11日間・8コミットが本番に出ていませんでした

2026年7月30日、自社の検索まわりを点検していて気づきました。Lifefolio(自分経営ゲーム)の本番が、2026年7月19日19時27分のデプロイを指したまま11日間据え置かれていたのです。

その間に積んだ8コミットが、本番に1つも出ていませんでした。内訳はこうです。

  • お知らせページの新設(7/22)
  • 最終更新日の表示(7/24)
  • ログイン画面のアクセシビリティ改善(7/28)
  • 検索面の統合・構造化データの整理(7/28)
  • ファビコン修正/配色のベージュ刷新/パスワード表示切替/ブランド検索まわりの調整

手元は正常でした。ローカルの main と origin/main は同じコミット(591a8f5)を指していて、GitHubには最新が届いていました。つまり①でも②でもなく、純粋な③だったわけです。

なぜ3サイクル続けて誤診したのか

いちばん痛いのはここです。私たちは7月28日・29日・30日の点検で3回続けて、「この変更はまだコミットできていない」「gitの承認が通らず在庫になっている」と記録していました。3回とも間違いでした。

誤診の理由は3つあります。

  1. 「pushした=本番に出た」と信じていた。この前提を一度も確かめていませんでした。
  2. デプロイ経路がプロダクトごとに違った。公式HPと先勝はGitHubとVercelが連携していてpushで自動反映されますが、LifefolioとTsumuは連携が無く、手動デプロイが実態でした。同じ会社の中で経路が2種類あることに気づいていませんでした。
  3. 見ていたのが手元だけだった。点検の手順に「本番側を見る」工程が1つも入っていませんでした。
「pushした=本番に出た」ではありません。デプロイは必ず、本番の実際のHTMLか、いつの版かを示すヘッダで確認します。 —— JurisCode.AI 編集部・開発ノート(2026-07-30)

自動化した処理が静かに止まっていても誰も気づかない、という失敗の型については 無言の失敗をどう見つけるか でも書きました。今回はその同じ型を、デプロイという一番外側で踏んだ形です。

gitコマンドを使わずに切り分ける手順

再発防止に作ったのが deploy-freshness.mjs という小さな検査です。設計の制約が2つありました。

1つ目は、gitコマンドを呼べないこと。自動実行のセッションではgitが承認待ちで止まってしまい、検査そのものが動きません。2つ目は、Vercelの管理画面やAPIトークンに頼りたくないこと。認証が切れた瞬間に検査が死ぬからです。

そこで、両方を使わない方法に振り切りました。

3状態を外部コマンドなしで切り分ける
  1. .git/HEAD を読んで、いま何という枝にいるかを知る
  2. .git/refs/heads/<枝> と .git/refs/remotes/origin/<枝> を読んで文字列を比べる。違えば②未push(pushすれば出る)
  3. .git/logs/HEAD(reflog(新しいタブで開く)=枝が動いた履歴)の末尾から commit の行をさかのぼり、最新コミットの時刻を取る
  4. 本番URLへHTTPのHEADリクエストを1回だけ投げ、age(この版をどれだけ保持しているかの秒数)と etag(版の指紋)を読む
  5. 最新コミットの時刻が、本番の版の観測時刻より24時間以上新しければ③未デプロイと判定する

`age` ヘッダ(新しいタブで開く)は、配信網のキャッシュが消えるとリセットされるので、単体だと「実際より新しい」と見えてしまいます。そこで etag が前回と同じなら前回の観測時刻を引き継ぎ、「本番が最後に変わったのを見た時刻」を日ごとに育てる作りにしました。検査は1日3回、既存の健康診断ジョブの中で自動実行しています。新しい通知先は作りませんでした。毎日必ず開くレポートに出ないアラートは、いずれ読まれなくなるからです。

検査を作ったあとに、自分で3回だまされました

ここが、この話でいちばん役に立つ部分かもしれません。作った検査にはバグが3つあり、3つとも「異常を見逃す方向」でした。

  • 正規表現の取りこぼし:reflogの行を commit(空白か括弧) という条件で拾っていたため、commit: という書式を取りこぼしました。結果、さかのぼって初回コミット(6月8日)を「最新コミット」と誤認し、本番のほうが新しいことになって全サイトが緑になっていました。
  • 判定不能を緑と言っていた:最新コミットの時刻が読めない場合にも「同期」と出していました。
  • 永久に赤にならない経路:etag が返らず age も0のとき(キャッシュ未命中のとき)、版の時刻は分からないのに緑を出していました。そのサイトは、どれだけ古くても未来永劫アラートが出ない状態でした。

3つとも、正常な状態で動かしている限り見つかりません。気づけたのは、手元に既知の異常(Lifefolioの11日)があり、それを検出できるかで試したからです。

日付の扱いを1行間違えただけで毎朝のレポートが8日間ずれ続けた話は 自動化スクリプトの日付が1日ずれる に書きましたが、あのときも「静かな誤り」でした。検出器そのものが静かに壊れると、被害は倍になります。

今日の実走結果(2026-09-24 08:10 JST)

記事を書くにあたって、その場で回した結果をそのまま貼ります。

✅ 同期  公式HP  https://juriscodeai.com/
    本番: HTTP 200 / cache HIT / この版を保持 0.1時間
    最新コミット: 2026-09-24 07:39 JST / push 済

✅ 同期  先勝  https://sensho.juriscodeai.com/
    本番: HTTP 200 / cache HIT / この版を保持 2.9日
    最新コミット: 2026-09-21 10:22 JST / push 済

✅ 同期  Lifefolio  https://lifefolio.juriscodeai.com/lp
    本番: HTTP 200 / cache HIT / この版を保持 14.0日
    最新コミット: 2026-09-07 17:15 JST / push 済

⚠️ 判定不能  Tsumu  https://tsumu.juriscodeai.com/
    本番: HTTP 200 / cache MISS / この版を保持 0.0時間
    → 本番の版がいつのものか特定できない(etag 無し・age 0)

総括: 🔴 0件 / 4サイト

読み方で1つだけ注意があります。Lifefolioは「この版を保持14.0日」と出ていますが、これは異常ではありません。最新コミットが9月7日なので、本番が14日古いのは正しい状態です。古いことと、出ていないことは別です。この2つを混同すると、直す必要のないものを直しに行くことになります。

Tsumuの「判定不能」も、あえて残している出力です。分からないときに緑と言わない、という設計のとおりに動いている証拠だからです。

切り分けたあと、状態ごとにやること

3状態のどれかが確定したら、行き先はこうなります。①未コミットなら、コミットしてから②の判定をもう一度回します。②未pushなら押し出すだけですが、承認や認証で止まっているならそこを先に解きます。

③未デプロイだけは、手元をいくら触っても動きません。ここではじめて、Vercelの公式ドキュメント(Git連携)(新しいタブで開く)を開き、本番ブランチの設定・ドメインに紐づく枝・連携の生死を順に確認します。

③にたどり着いてはじめて、世の中の記事でよく紹介されている「本番ブランチの設定が違う」「連携が外れている」といったチェックが意味を持ちます。逆に言えば、切り分けの前にその一覧を上から試すのは、たいてい時間の無駄です。私たちは3日分それをやりました。

無料枠と少人数でプロダクトを何本も動かす構成については 無料枠だけで本番運用する構成 にまとめています。本数が増えるほど、経路の違いは静かに事故になります。

よくある質問

Q. いちばん簡単に③未デプロイを見分ける方法はありますか。

A. 本番URLに対してHTTPのHEADリクエストを1回投げ、返ってくる etag を記録しておくのが手軽です。コードを変えてpushしたのに etag が翌日も同じなら、出ていない可能性が高くなります。管理画面を開かずに済むので、自動化にも載せやすい方法です。

Q. 手元のファイルを直接読む方式は安全ですか。

A. 読むだけなので、リポジトリの状態を壊すことはありません。ただし origin 側の参照は、pushやfetchが成功したときにしか更新されない点に注意してください。長くfetchしていないと、実際の状態とずれている場合があります。

Q. 「本番が古い」と「本番に出ていない」はどう違うのですか。

A. 新しいコミットが無ければ、本番が何日古くても正常です。比べるべきは「最新コミットの時刻」と「本番の版の時刻」で、前者のほうが十分に新しいときだけ異常になります。私たちの検査は、その差が24時間を超えたときに赤を出す設定にしています。

Q. 検査を作ったのに見逃していた、という事態はどう防ぎますか。

A. 既知の異常を1つ手元に用意して、それを赤にできるかで試すのが確実です。正常な状態で緑が出ることを確認するだけでは、見逃す方向のバグはまったく検出できません。私たちの場合、3つのバグのすべてがこの方法で見つかりました。

Q. プロダクトが複数あるとき、何から整えるべきですか。

A. まず、プロダクトごとのデプロイ経路(自動なのか手動なのか)を1か所に書き出すことをおすすめします。私たちが11日を見落とした根本は、経路が2種類あることを誰も書き残していなかった点にあります。そのうえで、毎日必ず見るレポートの中に鮮度の行を1つ足せば、翌日には気づける状態になります。

コメント

コメントは即時公開されます。不適切な内容は予告なく削除します。

#開発の裏側#Vercel#デプロイ