Engineering Postmortem

生成物を git 管理すると、stash が 22 本溜まる

rei-aios に 2 ヶ月で 22 本の stash が累積した。全件の中身を実測したところ、 実作業を含むものは 1 本も無かった。原因は 1 つの構造欠陥で、 同じ日に別途発生していた 19 file の unmerged 放置も、同じ欠陥の別の症状だった。

2026-08-15  ·  rei-aios  ·  調査対象 2026-06-21 → 2026-08-15

22累積していた stash
0うち実作業を含むもの
84%tracked file のうち
機械生成物
3.8 GB.git のサイズ

症状

git stash list に 22 本。最古は 2026-06-21、最新は 2026-08-15。 名前は temp-stash-step1333all unstaged for pullcron-owned files not in my scope といった調子で、 名前からは中身も捨ててよいかも判断できない

同じ日、別件で dist-renderer/oukc/ 以下 19 file が unmerged のまま 放置されているのも見つかった。当初この 2 つは無関係な問題に見えていた。

計測

まず 22 本すべての file list を dump して分類した。結果は予想よりはるかに偏っていた。

分類本数
完全に自動生成 (data/ dist-renderer/ snapshots/ のみ)17
自動生成 + 生成物 4 file4
untracked のみ (全て data/)1
実作業 (手書き code, test, 証明)0

「自動生成以外」に見えた 4 file — metadb.htmlmetadb-manifest.jsonsrc/renderer/rei-stats.jsontools/onshoku-jiten/index.html — も、追跡すると全て generator script が実在した。 src/renderer/rei-stats.json に至っては、commit 名がそのまま Auto: rei-stats refresh である。

つまり 22 本 6,700 file の全てが再生成可能だった。ではなぜ溜まったのか。repo の構成を見ると理由がはっきりする。

tracked file 34,516 件の内訳

rei-aios / 2026-08-15

69.5% data/ 14.5% 16.0% source
data/ — cron 生成データ dist-renderer/ — site build 出力 その他 — 人間が書く領域
▸ 表で見る
領域file 数占有率
data/24,00069.5%
dist-renderer/4,98814.5%
その他 (source, scripts, docs …)5,52816.0%
合計34,516100%

人間が書く領域は 16% しかない。残る 84% は cron が回すたびに書き換わる。 実際、直近 200 commit のうち ちょうど半分の 100 件Auto: 始まりの自動 commit だった。

因果連鎖

決め手になったのは「stash を作っている script が 1 本も無い」という事実だった。 scripts/ .github/ .claude/ を全走査して git stash の hit はゼロ。 22 本すべて、人間か agent が手で打ったものである。なぜ手で打つ必要があったのか。

  1. repo の 84% が機械再生成 file なのに tracked されている
  2. cron が常時 350〜460 file を書き換える → worktree が clean になる瞬間がない
  3. push しようとすると必ず cannot rebase: You have unstaged changes で停止する
  4. rebase.autoStash が未設定 → 毎回手で git stash を打つ
  5. push 後に pop すると cron の新しい出力と conflict → 解決を諦めて放置
  6. 2 ヶ月 × 2〜3 日おき = stash 22 本
そして [5] は、別件だと思っていた 19 unmerged そのものだった。 あちらは git stash pop の conflict が index に残ったまま放置された状態で、 MERGE_HEAD も rebase marker も存在しなかった。中断中の merge ではなく、 pop の残骸である。stash 累積と unmerged 放置は別々の問題ではなく、 同一欠陥の 2 つの症状だった。

対処

即効: 連鎖 [4] を切る

git config rebase.autoStash true
git config pull.rebase true

これだけで [4] → [5] → [6] が繋がらなくなる。git が自動で退避と復帰を行うので、 手で git stash を打つ理由そのものが消える。 1 行の設定漏れが 2 ヶ月で 22 本を生んでいた。

破棄前の退避は patch ではなく ref で

22 本を捨てる前に退避したが、patch export は採らなかった。試算すると 1 本で 12 MB、22 本で約 200 MB を repo に恒久的に載せることになり、 しかも stash の untracked file (3rd parent) の再現性が落ちる。

git update-ref refs/attic/stash-2026-08-15/05 <stash-sha>
方式repo への追加untracked の保持忠実度
patch export≒ 200 MB落ちる部分的
永続 ref0 byte完全完全

object は既に DB 内にあるので追加コストはゼロ、ref なので gc 対象外、 git stash store でいつでも stash list に戻せる。

命名規約

autoStash で吸収できない場合のみ stash を作り、prefix を必須とする。

種別形式保持期間
cron 生成物の退避auto:cron-YYYY-MM-DD-HHMM7 日
実作業の一時退避wip:STEP<n>-<説明>30 日で棚卸し

教訓

1. 「stash が溜まる」は運用の怠慢ではなく、構造の症状として読む。 22 本を「片付いていない」と見ると、片付けて終わる。実測して連鎖を辿ると、 設定 1 行の欠落に行き着き、しかも別の incident まで同時に説明できた。

2. 生成物を tracked にすると、worktree が clean になる瞬間が消える。 git の branch 操作はほぼ全て「clean な worktree」を前提にしている。 その前提を恒久的に壊すと、あらゆる操作の前に退避が要る状態になる。 84% という数字はその前提が壊れきっていることを示していた。

3. 捨てる前の退避は、形式で忠実度が変わる。 patch は人間には読めるが、untracked file を落とす。 ref は読みにくいが完全で、しかも 0 byte。捨てる決断の質は、 退避の質が担保する。