KiSAKU
技術系6分で読めます

通ったビルドが、何も直していなかった

公開

ビルドが通った。テストも落ちていない。デプロイも成功した。

それでも、直したかったものは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"

デプロイの遅れかと思い、キャッシュを避けて何度か取り直しました。変わりません。

コミットの中身を見た

ここでようやく、コミットに何が入っているかを確認しました。

git show –stat
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 に答えがありました。

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 → コミット、という順番で進んでいました。

reset は「取り消し」ではなく「破棄」

直前の操作を取り消したいときに git reset --hard を使いたくなりますが、これはコミット済みの履歴を戻す道具です。まだコミットしていない作業は、履歴に無いので戻せません。作業を残したまま状態を整えたいなら git stash のほうです。

なぜ「成功」してしまったのか

ここが今回いちばん考えさせられた点です。

変更が消えているのに、なぜビルドが通ったのか。答えは、消えた結果として、全部が整合していたからです。

クエリ側 生成された型 結果
意図した状態 first: 100 first: 100 通る
実際の状態 first: 20 first: 20 通る
もし片方だけ残っていたら first: 100 first: 20 落ちる

中途半端に消えていれば、ビルドが落ちて気づけました。きれいに全部消えたので、辻褄が合ってしまったわけです。

ビルドが検証しているのは「矛盾が無いこと」であって、「意図が反映されていること」ではありません。何も変えていないコードは、当然のように矛盾しません。

緑は「壊れていない」の意味

CIやビルドの成功は、壊れていないことの証明です。直っていることの証明ではありません。この2つを同じものだと思っていると、今回のように「全部が緑なのに何も進んでいない」状態を見逃します。

では、生成されたファイルの差分は何だったのか

もうひとつ不思議だったのが、消えているのに gql.ts だけ差分が出ていたことです。

調べると、生成されていたのは別のクエリの型でした。sitemap用に書いた、こういう文字列です。

sitemapPosts.ts
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つで走査対象になっていました。意図は、コードのどこかに具体的な形で表れていないと守られません。

← ブログ一覧へ戻る