Batch automatic post-commit reviews - #1088
Conversation
roborev: Combined Review (
|
Reviewing every commit gets noisy on repos with many small commits. A repo-local post_commit_batch_size lets the post-commit hook wait for N commits and enqueue one review over the accumulated range, without contacting the daemon below the threshold. Commit mode reviews checkpoint..HEAD; branch mode keeps the full branch scope at the configured frequency. Checkpoints are kept per branch in a JSON file under the shared git common dir, serialized by a file lock, and advance only after a successful enqueue. A new pre-push hook flushes pending work before commits leave the machine, including ancestor ranges the push carries; disabling batching drains the current range. Branch renames reconcile from reflog evidence, and any error fails open to an immediate review — worst case a redundant review, never a silently skipped commit. Generated with Claude Code (claude-fable-5) Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
6819d43 to
18ab7e3
Compare
roborev: Combined Review (
|
Branch renames and unrelated history rewrites could move a checkpoint past pending commits. Prefer rename evidence over a destination checkpoint, and recover unrelated histories from the rewritten root so no commits are skipped. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Reused branch names and concurrent checkouts could associate pending reviews with the wrong checkpoint or commit. Consume rename evidence once and resolve the captured branch ref so each batch stays tied to one branch history. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Recreated branch names must not inherit checkpoints from deleted refs. Track the observed reflog incarnation while preserving rename migration. Pre-push flushes also retain peeled commits from non-branch refspecs as candidates. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Cross-branch flushes must not reuse configuration from the pushing checkout. Use the target branch worktree when it exists, and defer unopened branches without advancing their checkpoints. Missing reflog history is not proof that a branch was recreated. Preserve the existing checkpoint so a possible repeat review replaces skipped commits. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
The batching design promises that the worst case is a redundant review, never a skipped commit. The reflog-evidence layer (rename-source chains, branch incarnation identities) inverted that bias: inconclusive evidence discarded checkpoints, so a rename with an expired reflog could silently skip pending commits, while the machinery itself only prevented redundant reviews the design already accepts. Replace it with one rule biased the safe way: a stored entry whose branch name no longer resolves and whose checkpoint lies on the current branch's first-parent chain is adopted when it widens the pending range, and dropped when the range already covers it. Off-chain orphans are left for a branch on their own history. No reflog parsing, no incarnation state. Pre-push flushes of branches with no checked-out worktree now run from the pushing worktree instead of deferring, so pushed commits never leave the machine unreviewed.
roborev: Combined Review (
|
An entry recorded only its range's start, so three sequences could silently skip commits: a branch name reused after a rename hid the pre-rename range from both branches; a parent branch planning first adopted a renamed branch's orphaned checkpoint and stranded its pending commits; and the rebase guard dropped pre-push branch flushes, letting pending commits be pushed unreviewed. Each entry now also records the branch head last observed (its tip), naming the history the range belongs to. Adoption requires the tip on the adopter's first-parent chain, so shared-ancestor checkpoints stay with their own branch. A live branch whose recorded tip escaped its history hands the range to the branch that holds it before continuing with its own accounting; plain rewrites, where no branch holds the old tip, keep merge-base recovery unchanged. Bare-checkpoint state files from earlier builds still load. Pre-push flushes carry an explicit branch and pushed SHA, so the rebase guard now applies only to ordinary post-commit invocations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
A tip recorded only at seed and advance goes stale while below-threshold commits accumulate, understating the pending range: after a rename, a branch forked at the stale tip could adopt and delete the entry, dropping the later commits from batch state. The entry now refreshes its tip to the observed head on every post-commit plan; the checkpoint still advances only after a successful enqueue. Ancestor flushing skipped entries whose branch name no longer resolves, so a tag or SHA push carrying a renamed branch's pending commits left them unreviewed. Such entries now fall back to their recorded tip, deferring to a pushed branch whose first-parent chain holds the tip so the range keeps its live-branch attribution when one exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
Advancing a batch entry wrote both checkpoint and tip as the flushed head, so a partial flush at a child fork point clobbered a descendant tip and understated the orphan's remaining range: a later tag or SHA push of the original tip saw nothing pending and let those commits leave unreviewed. Advance now preserves a recorded tip that is still ahead of the new checkpoint, so the remainder stays flushable. Rewritten histories, where the old tip is not a descendant, still reset to the flushed head. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
The ancestor flush accepted any merge base past the checkpoint, including side history reachable from the saved tip only through a merge's second parent. Pushing a merged side branch then advanced the parent's checkpoint onto that side history — off its first-parent chain — and once the parent was renamed, the adoption tip gate correctly refused the corrupted entry, stranding the merge commit to be pushed without review. Accept a boundary only when it lies on the saved tip's first-parent chain, matching every other range decision in the batch state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
The pre-push flush trusted a live branch ref over the recorded range. When a branch is force-moved onto divergent history, its pending commits exist only in the recorded entry, so pushing a tag or SHA at the old tip carried them out with no review. Flush candidates now include the recorded tip when it left the live first-parent chain, and ancestor flushes run as separate invocations so a branch pushed by name can also flush an abandoned range in the same push. The batch lock was the one unbounded wait left in the hook, which is supposed to never block a commit: a suspended hook holding the lock would hang every later commit. Lock acquisition now gives up after ten seconds. Bailing is safe because the waiter's checkpoint is unchanged, so the next hook run retries the same range. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
Bounding the batch lock wait turned a rare hang into a dropped review: a hook that gave up on the lock returned without enqueueing anything. In immediate mode — the default, with batching off — no later hook retries a dropped commit, so that review was lost for good. This also broke the hook's stated contract that any error falls back to an immediate single-commit review. A lock timeout on an ordinary post-commit run now enqueues an immediate HEAD review without reading or writing batch state, so it cannot corrupt a concurrent hook's plan. Batching users get at most a redundant review of that commit in the next batch. Flush invocations still skip on lock failure: their pending range is recorded, and a later commit or push retries it. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
A branch force-moved onto divergent history keeps its pending review range only in the recorded entry, and a tag or SHA push of the old tip must still flush it. That worked when the histories shared a root, but first-parent distance fails with an error on unrelated histories instead of answering off-chain, so a branch moved onto an orphan root hid its recorded range and the old commits could be pushed unreviewed. A recorded tip that still resolves but is not on the live first-parent chain now counts as abandoned history in both cases. An unresolvable tip still means the commits are pruned and nothing remains to flush. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
roborev currently queues a review after every commit. That gets noisy fast on repos with lots of small commits.
This adds a repo-local
post_commit_batch_size, so the post-commit hook waits for N commits and enqueues one review over the accumulated range. Below the threshold the hook returns without starting or contacting the daemon, so sub-threshold commits stay cheap. Commit mode reviewscheckpoint..HEAD; branch mode keeps the full branch scope, just at the configured frequency. Disabling batching drains the current range instead of dropping it.Checkpoints are kept per branch in a JSON file under the shared git common dir, so linked worktrees agree on the same pending range. Each entry records its pending range — the last reviewed boundary and the branch head last observed — so a range is tied to a history, not just a branch name. Hook runs are serialized with a file lock, and a checkpoint advances only after a successful enqueue, so a failed enqueue retries the same range on the next commit. A new pre-push hook flushes pending work for pushed
refs/headsbranches before they leave the machine; a pushed branch with no checked-out worktree is flushed from the pushing worktree, and explicit branch flushes run even during a rebase.There is deliberately no reflog or rename tracking: after a rebase or amend the checkpoint recovers from the merge base and re-reviews from there, and any other error fails open to an immediate single-commit review. A rename leaves its entry orphaned and the branch whose first-parent chain holds the recorded range adopts it; a branch name reused after a rename hands the old range to the branch that now holds it. The worst case is a redundant review, never a silently skipped commit.