Skip to content

fix(reviewer): bound the PR-head snapshot cache to open-PR heads - #171

Merged
asavs merged 1 commit into
mainfrom
fix/bound-snapshot-cache
Jul 12, 2026
Merged

asavs merged 1 commit into
mainfrom
fix/bound-snapshot-cache

Conversation

@asavs

@asavs asavs commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Root cause

prepare_review_worktree() is a write-only cache: every first review of a head SHA downloads and extracts a ~118MB PR-head tarball into $RUNTIME_STATE_DIR/worktrees/<slug>/heads/<sha>, chmod -R a-ws it, and nothing ever deletes it. Snapshots are only reused when a review of the same head SHA is retried on a later tick; once a PR is re-committed, merged, or closed, its snapshot is dead weight forever. On the deployed e2-micro this grew to 6+GB and filled the 19GB disk.

Fix: prune to the live open-PR head set every tick

New prune_stale_review_worktrees() in lib/worktree.sh takes the set of head SHAs to keep and deletes every heads/<sha> directory not in it (restoring u+w first, since snapshots are created a-w). reviewer.sh calls it once per tick after the review loop, so the cache is bounded by the open-PR working set instead of growing without eviction.

Two correctness points:

  • The keep-set is parsed from $PRS (field 3 of every row), not accumulated inside the review loop. The loop can break early when it hits the attempt budget (review_one_pr_status -eq 10), so an in-loop accumulation would omit PRs never reached this tick and wrongly evict their still-valid snapshots. An empty $PRS (no open PRs) yields an empty keep-set and evicts everything, which is correct.
  • Pruning is skipped in the single-PR paths (ONLY_PR / DRY_RUN / RENDER_PROMPT_ONLY), which can populate $PRS with a single PR — pruning there would evict every other open PR's cache.

Secondary fix: deterministic runtime dir

RUNTIME_OWNER was ${USER:-$(id -u ...)}: cron runs with $USER unset (→ uid), interactive runs have it set (→ username), so the daemon grew two parallel runtime dirs and pruning one leaves the other to rot. The suffix is now pinned to the uid first ($(id -u 2>/dev/null || printf '%s' "${USER:-user}")"), so both entry points resolve the same dir. The REVIEWER_RUNTIME_STATE` override still wins, unchanged.

Tests

New fixture test_prune_stale_review_worktrees in tests/fixtures/worktree-state.sh builds real snapshots via prepare_review_worktree (so the trees are genuinely chmod a-w) and covers: keep-set survival, stale-SHA eviction, the restore-write-then-delete path on read-only trees, empty keep-set evicting everything, and missing heads/ as a return-0 no-op. Full suite: passed 577 fixture assertions (matches pinned EXPECTED_ASSERTIONS); bash -n clean on all 49 shell files.

Cached PR-head snapshots (~118MB each) were never evicted, filling the
e2-micro's 19GB disk. Each tick now prunes every heads/<sha> snapshot
whose SHA is not the head of an open PR, with the keep-set parsed from
the full PR queue rather than the review loop (which can break early on
the attempt budget). The runtime-dir suffix is pinned to the uid so cron
(no $USER) and interactive runs share one cache instead of growing two.
@asavs
asavs merged commit 4796956 into main Jul 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant