通ったビルドが、何も直していなかった
ビルドが通った。テストも落ちていない。デプロイも成功した。
それでも、直したかったものは1つも直っていませんでした。
変更が丸ごと消えた状態でコミットが成功してしまった話と、それがなぜ「成功」に見えたのか。そして、こういう失敗を見つけるための確認が「動くかどうか」ではなく「入っているかどうか」だという話です。
やろうとしていたこと
2つの修正を、1回の作業でまとめる予定でした。
| 直したいもの | 理由 |
|---|---|
| 記事一覧の取得上限(20→100) | 公開記事がちょうど20本。次の1本から漏れ始める |
| 構造化データの日時をGMTに | 時差の情報が無い時刻を、機械に渡していた |
どちらもGraphQLのクエリを変える作業です。このプロジェクトはクエリから型を自動生成しているので、クエリを変えたら型を作り直す必要があります。作り直しにはCMSへの接続が要るため、手順はこうなりました。
1. ソースを5ファイル書き換える
2. npm run codegen (型を作り直す)
3. npm run build (手元で通ることを確認)
4. commit → push
全部が「成功」した
実行結果は、どこにも問題がありませんでした。
codegenは通りました。ビルドも通りました。コミットもpushも成功し、Vercelのデプロイも緑になりました。本番サイトは変わらず表示されています。
ところが本番の構造化データを見ると、日時が変わっていません。
期待 "datePublished": "2026-08-22T04:20:00Z"
実際 "datePublished": "2026-08-22T13:20:00"
デプロイの遅れかと思い、キャッシュを避けて何度か取り直しました。変わりません。
コミットの中身を見た
ここでようやく、コミットに何が入っているかを確認しました。
frontend/src/gql/gql.ts | 5 +++++
frontend/src/gql/graphql.ts | 8 +++++++-
2 files changed, 12 insertions(+), 1 deletion(-)
2ファイル。しかも両方とも、自動生成されたファイルです。手で書き換えたはずの5ファイルが、1つも入っていません。
作業ディレクトリを見ると、posts(first: 20) のまま戻っていました。書き換えたはずの内容が、どこにも残っていません。
何が起きていたか
git reflog に答えがありました。
d370f37 HEAD@{0}: commit: fix: 記事一覧の上限を100に上げ...
682faf3 HEAD@{1}: reset: moving to HEAD ← これ
682faf3 HEAD@{2}: pull: Fast-forward
pullの直後、コミットの前に reset が挟まっています。git reset --hard は、コミットしていない変更を問答無用で消します。元に戻す手段はありません。
つまり、書き換え → リセットで消滅 → その状態でcodegen → コミット、という順番で進んでいました。
直前の操作を取り消したいときに git reset --hard を使いたくなりますが、これはコミット済みの履歴を戻す道具です。まだコミットしていない作業は、履歴に無いので戻せません。作業を残したまま状態を整えたいなら git stash のほうです。
なぜ「成功」してしまったのか
ここが今回いちばん考えさせられた点です。
変更が消えているのに、なぜビルドが通ったのか。答えは、消えた結果として、全部が整合していたからです。
| クエリ側 | 生成された型 | 結果 | |
|---|---|---|---|
| 意図した状態 | first: 100 | first: 100 | 通る |
| 実際の状態 | first: 20 | first: 20 | 通る |
| もし片方だけ残っていたら | first: 100 | first: 20 | 落ちる |
中途半端に消えていれば、ビルドが落ちて気づけました。きれいに全部消えたので、辻褄が合ってしまったわけです。
ビルドが検証しているのは「矛盾が無いこと」であって、「意図が反映されていること」ではありません。何も変えていないコードは、当然のように矛盾しません。
CIやビルドの成功は、壊れていないことの証明です。直っていることの証明ではありません。この2つを同じものだと思っていると、今回のように「全部が緑なのに何も進んでいない」状態を見逃します。
では、生成されたファイルの差分は何だったのか
もうひとつ不思議だったのが、消えているのに gql.ts だけ差分が出ていたことです。
調べると、生成されていたのは別のクエリの型でした。sitemap用に書いた、こういう文字列です。
const POSTS_FOR_SITEMAP = /* GraphQL */ `
query GetPostsForSitemap {
posts(first: 100) { ... }
}
`;
このファイルは、型生成に依存したくないからこそ素の文字列で書いていました。ところが先頭に付けた /* GraphQL */ という目印を、codegenが「これはクエリだ」と認識して走査対象に含めていました。
結果として、型生成を避けるために作ったファイルが、型生成の対象になっていたわけです。意図とは正反対でした。
この矛盾は、単体では表に出ません。使われない型がひっそり生成されるだけで、何も壊れないからです。別の失敗を調べていて、たまたま見つかりました。目印を外して、対象から除きました。
次から何を見るか
再発を防ぐために増やした確認は、1つだけです。
git status --short
並んでいるファイルの数を数えます。5ファイル触ったなら、5ファイル並んでいるはずです。2つしか無ければ、その時点でおかしい。
やり直したときは、実際にこれで確認しました。7ファイル(手で書いた5つ + 生成された2つ)が並んでいることを見てからコミットしています。
| 1回目 | 2回目 | |
|---|---|---|
| コミットに入ったファイル | 2 | 7 |
| ビルド | 通った | 通った |
| 本番の日時 | 変わらず | Z が付いた |
1行目だけが、両者を区別できていました。
今回の学び
まず、ビルドの成功は「壊れていない」であって「直っている」ではないこと。今回は変更が丸ごと消えたことで整合が取れてしまい、すべての工程が緑になりました。矛盾を検査する仕組みは、何もしていない状態を歓迎します。
次に、結果ではなく、入力を確認すること。「本番の表示が変わったか」で気づくのは最後です。コミットに何ファイル入ったかを見れば、pushする前に止められました。確認は下流より上流に置くほうが安いです。
そして、git reset --hard は取り消しではないこと。コミットしていない作業に対しては、単なる破棄です。状態を整えたいだけなら stash を使う、という区別を持っておくと事故が減ります。
最後に、避けたはずの仕組みに、目印ひとつで巻き込まれること。型生成から独立させるつもりのファイルが、コメント1つで走査対象になっていました。意図は、コードのどこかに具体的な形で表れていないと守られません。
この記事を共有する
関連記事

あと34本で、記事が消え始める作りだった
件数の上限を置いたまま忘れると何が起きるか。なぜ気づけないのか。そして上限を上げるのではなく、外した話です。

Criticalが4件並んだ日にやったこと
朝、通知を見たらCriticalが4件並んでいました。どれも同じ相手、Next.js本体です。 この日にやったことを順に書きます。結論から言うと、上げました。ただ、上げる前にやったことのほうが本題です。 この記事で扱うこ […]

再試行は入れていた。それでもデプロイが落ちた
再試行の処理は、半年前から入れていました。CMSが500を返したら、少し待ってもう一度投げる。3回まで。 それでも、デプロイが落ちました。 この記事で扱うこと 再試行を入れてあったのに効かなかった話です。何が返ってきてい […]