Engineering Postmortem
rei-aios に 2 ヶ月で 22 本の stash が累積した。全件の中身を実測したところ、 実作業を含むものは 1 本も無かった。原因は 1 つの構造欠陥で、 同じ日に別途発生していた 19 file の unmerged 放置も、同じ欠陥の別の症状だった。
git stash list に 22 本。最古は 2026-06-21、最新は 2026-08-15。
名前は temp-stash-step1333、all unstaged for pull、
cron-owned files not in my scope といった調子で、
名前からは中身も捨ててよいかも判断できない。
同じ日、別件で dist-renderer/oukc/ 以下 19 file が unmerged のまま
放置されているのも見つかった。当初この 2 つは無関係な問題に見えていた。
まず 22 本すべての file list を dump して分類した。結果は予想よりはるかに偏っていた。
| 分類 | 本数 |
|---|---|
完全に自動生成 (data/ dist-renderer/ snapshots/ のみ) | 17 |
| 自動生成 + 生成物 4 file | 4 |
untracked のみ (全て data/) | 1 |
| 実作業 (手書き code, test, 証明) | 0 |
「自動生成以外」に見えた 4 file — metadb.html、metadb-manifest.json、
src/renderer/rei-stats.json、tools/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
| 領域 | file 数 | 占有率 |
|---|---|---|
| data/ | 24,000 | 69.5% |
| dist-renderer/ | 4,988 | 14.5% |
| その他 (source, scripts, docs …) | 5,528 | 16.0% |
| 合計 | 34,516 | 100% |
人間が書く領域は 16% しかない。残る 84% は cron が回すたびに書き換わる。
実際、直近 200 commit のうち ちょうど半分の 100 件が
Auto: 始まりの自動 commit だった。
決め手になったのは「stash を作っている script が 1 本も無い」という事実だった。
scripts/ .github/ .claude/ を全走査して git stash の hit はゼロ。
22 本すべて、人間か agent が手で打ったものである。なぜ手で打つ必要があったのか。
cannot rebase: You have unstaged changes で停止するrebase.autoStash が未設定 → 毎回手で git stash を打つgit stash pop の conflict が index に残ったまま放置された状態で、
MERGE_HEAD も rebase marker も存在しなかった。中断中の merge ではなく、
pop の残骸である。stash 累積と unmerged 放置は別々の問題ではなく、
同一欠陥の 2 つの症状だった。
git config rebase.autoStash true
git config pull.rebase true
これだけで [4] → [5] → [6] が繋がらなくなる。git が自動で退避と復帰を行うので、
手で git stash を打つ理由そのものが消える。
1 行の設定漏れが 2 ヶ月で 22 本を生んでいた。
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 | 落ちる | 部分的 |
| 永続 ref | 0 byte | 完全 | 完全 |
object は既に DB 内にあるので追加コストはゼロ、ref なので gc 対象外、
git stash store でいつでも stash list に戻せる。
autoStash で吸収できない場合のみ stash を作り、prefix を必須とする。
| 種別 | 形式 | 保持期間 |
|---|---|---|
| cron 生成物の退避 | auto:cron-YYYY-MM-DD-HHMM | 7 日 |
| 実作業の一時退避 | 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。捨てる決断の質は、 退避の質が担保する。