Repository navigation
Open
Labels
Description
Background
V2 needs an explicit policy for pruning filesystem snapshots from disk. Today the legacy snapshot store writes Git tree objects under the per-project/worktree snapshot gitdir and runs git gc --prune=7.days on a background loop. That is age-based Git object cleanup, not reachability from durable session state.
If V2 stores snapshot hashes in session messages/revert metadata and sessions are indefinitely resumable from the database, Git does not know those DB references are live. A 7-day prune can therefore delete snapshot tree/blob objects that old-but-resumable sessions still need for diff/revert/undo.
Related prior evidence:
- Review panel shows no diffs: snapshot tree objects garbage collected by git gc --prune=7.days #18734: snapshot tree objects were garbage-collected by
git gc --prune=7.days, breaking review diffs. - Snapshot directory can grow to 40GB+ in 7 days due to hardcoded prune=7.days #17397 / Snapshot feature consumes excessive disk space (170GB+ in my case) #17753 / Snapshot feature consumes 533GB+ of disk due to orphaned git temp files and unbounded growth #15977: snapshot storage can also grow unboundedly or excessively.
Question to settle
What guarantee should V2 make for snapshot-backed file history?
Options:
- Strong default: if a session is still in the DB and exposes snapshot-backed history, referenced snapshots remain available.
- Weak/cache default: old sessions remain resumable as chat, but old snapshot-backed file diff/revert/undo is best-effort and may disappear.
- Bounded default: snapshot history is retained for an explicit window/budget/session lifecycle, and the UI/API degrades clearly after that boundary.
Likely implementation direction if we choose the strong default
- Treat the DB/session tables as the source of live snapshot hashes.
- Add a
SnapshotGC/maintenance job that scans reachable snapshot hashes from session messages, parts, revert state, summaries, etc. - Pin live hashes in the snapshot gitdir with Git refs so
git gccan preserve them.- One possible shape: create a tiny commit pointing at the snapshot tree and update
refs/opencode/snapshots/<hash>to that commit. - The DB can continue to store the original tree hash; the ref/commit is only a reachability pin.
- One possible shape: create a tiny commit pointing at the snapshot tree and update
- Remove pins for hashes no longer referenced by retained session state.
- Keep
git gc --prune=7.daysor similar only as orphan cleanup / race grace, not as the semantic retention guarantee.
Things to decide
- Are all DB sessions considered retained forever, or do we have/need session archival/deletion semantics?
- Does compaction ever deliberately drop old file-history capability? If yes, it should remove snapshot references or mark them collectible.
- Should snapshot retention be user-configurable by age/size, and if so is that a weaker opt-in mode?
- What should the product show when a snapshot reference is missing: unavailable diff/revert, degraded resume, repair prompt, etc.?
- How do we migrate/handle existing snapshot hashes that may already be unpinned or missing?
Acceptance criteria
- V2 has a documented snapshot retention guarantee.
- The pruning job cannot collect snapshot objects still referenced by sessions under that guarantee.
- Orphaned/unreferenced snapshot objects are eventually reclaimable.
- Missing snapshot objects degrade explicitly rather than failing silently.
Activity
Dante-dan commentedon Oct 2, 2026
I suggest a strong guarantee for available snapshots referenced by recoverable durable session state, with a first slice that only adds protection. #51996 explicitly leaves retention/GC policy out; its capture/compaction work would remain separate.
Proposed contract:
start/endand the session's revert snapshot are roots. The root inventory must also include snapshot references in durable events used for supported replay/recovery: deleting a projected message row is not evidence that its objects are safe to collect.The first implementation would not remove pins or prune objects, change retention defaults, or add a disk budget. Compaction may pack objects but must preserve this reachable closure. Session deletion, event compaction, shared snapshot references/forks, and a later budget policy need an explicit release rule before collection is introduced; a database row's disappearance alone would not be that rule. A future budget should surface pressure rather than silently discard reachable undo history. This makes the initial change additive and keeps current capture/diff/restore behavior for available objects.
Acceptance cases for that first slice: a snapshot older than seven days survives an explicit Git prune/GC and still produces the same diff/restore result; restart preserves pins; two durable references to the same tree remain usable; revert state stays protected after message projection trimming; interruption between pinning and persistence leaves at worst an extra protected tree; interrupted backfill never enables deletion; and a missing legacy tree gives a recoverable unavailable result without changing the session or claiming a successful restore. The historical root inventory is a prerequisite to claiming migration coverage, not something this proposal assumes is already complete.
Could the core team review this strong-reachability contract and approve the non-destructive pinning/backfill first slice? In particular, should the supported replayable event history remain a root until an explicit event-retention decision releases it?
kobayashiHawtin commentedon Oct 11, 2026
Additional field evidence for the retention/GC gap (verified on v2.0.25, Windows 11).
Snapshot store (
%USERPROFILE%\.local\share\opencode\snapshot\) before cleanup:Nothing is reclaimed over months — the oldest per-project gitdir had not been touched since 2026-05-27 despite the machine being in active use.
Two concrete observations relevant to the policy question:
The per-project gitdir is keyed by a project-family hash, and one hash holds the snapshots of every worktree of that project. Example: a single hash held 13 snapshots / 243.8 MB for one project family. So the unit of reclaim is the project family, not the session or worktree.
There is no reconciliation against deleted sessions/worktrees. I found snapshot gitdirs whose
core.worktreeno longer exists on disk (e.g....\my_dev\_od-probe,...\my_dev\item-images); their newest snapshot was ~1 month old and nothing reaped them. So orphaned snapshots from removed projects persist indefinitely.I manually removed the stale/orphaned per-project gitdirs (133.8 MB freed), but nothing prevents unbounded regrowth: retention is only age-based at the Git object level (
git gc --prune=...), and there is no user-facingopencode snapshot prunecommand or age/size retention config (thesnapshotconfig is a single boolean).Interim mitigation question (same as the issue): would an explicit retention knob (age/size) or a maintenance command be acceptable before the reachability-pin design lands? That would let users bound the store without disabling snapshots entirely.
lovasoa commentedon Oct 22, 2026
If you are considering a GC, please also look at git worktrees disk usage ! If I'm correct, currently old git worktrees just accumulate forever.