Skip to content

Batch automatic post-commit reviews - #1088

Merged
wesm merged 13 commits into
mainfrom
feat/post-commit-batching
Aug 24, 2026
Merged

Batch automatic post-commit reviews#1088
wesm merged 13 commits into
mainfrom
feat/post-commit-batching

Conversation

@wesm

@wesm wesm commented Aug 22, 2026

Copy link
Copy Markdown
Member

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 reviews checkpoint..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/heads branches 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.

@wesm

wesm commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

Replaces #1044 (accidentally closed by a faulty force-push to its fork branch; fork push access was lost when it closed). Same squashed commit, review history lives in #1044.

@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (6819d43)

Pending reviews can be bypassed in branch-rename and non-branch push scenarios.

Medium

  • Stale destination checkpoints can skip pending commits after a branch renamecmd/roborev/postcommit_batch.go:197
    Rename migration is attempted only when the destination has no checkpoint or its checkpoint is off-chain. A stale checkpoint left by a deleted destination branch may lie on the renamed branch’s first-parent chain, causing the source branch’s earlier pending commits to be permanently skipped. This also affects disabled and pre-push planners. Consult rename evidence before trusting an existing destination checkpoint, preferring a usable source checkpoint, and cover normal planning, disabled draining, and push flushing.

  • Tag-only and raw-revision pushes can bypass pending-review flushingcmd/roborev/postcommit.go:261
    Pre-push flushing ignores non-deletion sources other than refs/heads/* and HEAD. Consequently, tag-only pushes and pushes from raw revisions can publish commits in pending batches without first flushing them. Retain every pushed object SHA for ancestor-boundary discovery while limiting direct branch flushes to named branch sources.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 8m12s

@wesm wesm self-assigned this Aug 23, 2026
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>
@wesm
wesm force-pushed the feat/post-commit-batching branch from 6819d43 to 18ab7e3 Compare August 23, 2026 11:59
@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (18ab7e3)

Medium-severity checkpoint migration and history-rewrite gaps could permanently skip commits during post-commit review.

Medium

  • cmd/roborev/postcommit_batch.go:197 and cmd/roborev/postcommit_batch.go:409 — Rename migration accepts a destination checkpoint whenever it lies on the renamed branch’s first-parent chain. If that checkpoint is stale but newer than the source checkpoint, earlier pending source commits are permanently skipped. Reconcile rename evidence before accepting any destination checkpoint, and test an on-chain destination checkpoint newer than the source checkpoint.

  • cmd/roborev/postcommit_batch.go:570 — When rewritten history has no merge base with the stored checkpoint, recovery reviews only head and preserves the unusable checkpoint. Following a root rebase or unrelated-history rewrite, other pending commits can remain permanently unreviewed. Remap the checkpoint using post-rewrite data or review the complete empty-tree/root-to-head range before advancing it.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 9m41s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (505b6ed)

Code review found two medium-severity correctness issues in post-commit batching.

Medium

  • Rename evidence can be reused indefinitelycmd/roborev/postcommit_batch.go:284
    If a renamed source branch is recreated, gains a checkpoint, and is later deleted, the renamed branch can adopt that unrelated checkpoint during a subsequent hook. This may overwrite its valid checkpoint and silently skip pending commits. Persist consumed rename migrations or otherwise distinguish the original rename checkpoint from later state under a reused source name. Add coverage for recreate-then-delete before the renamed branch’s next hook.

  • Branch and HEAD are resolved inconsistentlycmd/roborev/postcommit_batch.go:175
    The current branch and HEAD are captured by separate Git commands. A concurrent checkout between them can combine one branch’s name with another branch’s commit, enqueueing the wrong range and advancing the wrong checkpoint. Resolve the captured symbolic branch ref directly, or obtain both values through one consistent Git operation.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 11m28s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (54e0069)

Code review found two medium-severity correctness issues in branch batching and pre-push flushing.

Medium

  • cmd/roborev/postcommit.go:261 — Valid non-branch push sources are ignored. Pre-push input is discarded unless its source is refs/heads/* or exactly HEAD. Pushes using sources such as HEAD~1, raw commit refspecs, or tags can transfer commits while leaving pending branch batches unreviewed. Retain every non-deletion pushed commit as an ancestor-flush candidate by resolving or peeling it to a commit; use branch names only for direct checkpoint attribution. Add behavioral coverage for a non-branch source carrying pending commits.

  • cmd/roborev/postcommit_batch.go:208 — Checkpoints survive branch deletion and can be incorrectly reused. Because checkpoints are keyed only by branch name, recreating a branch with the same name adopts the previous branch incarnation’s checkpoint. This can produce an incorrect pending count and trigger an immediate, overly broad review. Persist sufficient ref-incarnation or last-observed-head state to detect deletion and recreation and initialize a fresh checkpoint, while preserving explicit rename migration. Add coverage for same-name recreation from related and divergent histories.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 10m28s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (a164a11)

Review found two medium-severity correctness issues in cross-branch configuration and checkpoint handling.

Medium

  • cmd/roborev/postcommit.go:285 — Incorrect configuration used when flushing another branch

    Cross-branch flushing uses the pushing checkout’s root for configuration resolution and daemon requests. When .roborev.toml differs between branches or linked worktrees, the target branch can receive the wrong review mode, agent, exclusions, or workflow. A mismatched excluded_branches policy may return HTTP 200 and advance the checkpoint without reviewing the pending commits.

    Suggested fix: Resolve and retain the target branch’s worktree/config provenance while planning its batch, then use that context consistently for enqueue policy and workflow resolution.

  • cmd/roborev/postcommit_batch.go:341 — Reflog expiry can discard pending commits

    A saved identity missing from a readable reflog is treated as definitive evidence that the branch was recreated. Normal reflog expiry or pruning can also remove old identities, causing a dormant pending batch to be discarded and reseeded at HEAD^1. This permanently omits earlier pending commits.

    Suggested fix: Treat a missing reflog identity as inconclusive and preserve, validate, or recover the existing checkpoint. Discard it only when branch recreation is positively established, or use an incarnation marker that does not expire with reflogs.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 18m17s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (798e75a)

Review verdict: Two medium-severity correctness issues could allow commits to bypass post-commit review.

Medium

  • Branch renames can silently skip pending commitscmd/roborev/postcommit_batch.go:301

    When the prior reflog identity has expired or been pruned, the code treats the resulting inconclusive match as a mismatch, discards the candidate checkpoint, and deletes its source state. The renamed branch then seeds at HEAD^1, potentially skipping pending pre-rename commits.

    Suggested fix: Preserve or migrate candidates that remain compatible with the renamed branch’s history when reflog identity is unavailable. Delete source state only when branch recreation is definitively established.

  • Unchecked-out branches can be pushed without reviewcmd/roborev/postcommit.go:285

    Pushing an explicitly named branch that is not checked out in any worktree defers its batch flush but still allows the push. Its commits can reach the remote unreviewed, with no guaranteed later event to flush them, contradicting the documented pre-push behavior.

    Suggested fix: Flush the branch using its pushed ref and branch-specific configuration, either by resolving configuration from that branch or by retaining the necessary configuration context with its checkpoint.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 8m13s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (65467fd)

Code review found two medium-severity correctness issues in post-commit batching and pre-push flushing.

Medium

  • Recreated branch names can permanently skip pre-rename commitscmd/roborev/postcommit_batch.go:291
    Ignoring checkpoints whose branch name remains live mishandles branch renames followed by recreating the old name. The renamed branch may seed at the previous tip while the recreated source retains the earlier checkpoint, leaving a pre-rename commit unreviewed by either branch. Preserve branch-incarnation or pending-tip information, or conservatively copy an on-chain checkpoint to the renamed branch without removing it from the recreated source.

  • Rebase guard prevents explicit pre-push flushescmd/roborev/postcommit.go:83
    The rebase guard also rejects the internal --flush --flush-branch invocation. If an unchecked-out branch is pushed while the fallback worktree is rebasing, the flush is skipped and pending commits can be pushed without review. Restrict the guard to ordinary post-commit invocations and allow flush requests carrying an explicitly captured branch and SHA.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 14m2s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (f4dcf1d)

The batching change has two medium-severity branch-history bugs that can silently skip pending reviews.

Medium

  • cmd/roborev/postcommit_batch.go:243 — Existing batch entries do not refresh Tip on ordinary below-threshold commits. After multiple pending commits and a branch rename, a branch containing the stale tip can adopt and delete the orphaned entry despite lacking later pending commits, causing those commits to disappear from batch state and be pushed without review. Update the current branch entry’s Tip to the captured head after every successful normal batch plan while preserving its checkpoint. Add a test covering a rename after multiple pending commits where a branch forked at the former tip runs first.

  • cmd/roborev/postcommit_batch.go:674 — Ancestor flushing ignores saved entries whose original branch name no longer resolves. After a branch rename or deletion, pushing pending commits through a tag, SHA, or another non-branch source skips the orphaned entry. Derive the carried boundary from the stored entry tip, or map that tip to a live containing branch, rather than requiring refs/heads/<savedBranch> to exist. Add a rename-plus-tag-push behavioral test.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 11m41s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (00bb3e5)

Review verdict: One medium-severity correctness issue should be addressed before merging.

Medium

  • Partial ancestor flush can leave later commits unreviewedcmd/roborev/postcommit_batch.go:815

    When a pending range A..C is partially flushed through a child fork at B, the state records both the checkpoint and tip as B. A later tag or SHA push containing C then appears to have no pending commits, leaving B..C unreviewed.

    Preserve the recorded descendant tip when flushing an older boundary, advancing only the checkpoint. Add a behavioral test covering a partial child push followed by a tag/SHA push of the original tip.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 9m51s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (380bd40)

Review identified a medium-severity correctness gap in branch checkpoint flushing.

Medium

  • cmd/roborev/postcommit_batch.go:702 — Ancestor flushing accepts any merge base after the checkpoint, including boundaries reachable only through the saved branch tip’s second parent. Pushing a merged side branch can therefore advance the parent branch’s checkpoint onto side history. If the parent branch is later renamed, its first-parent commits and merge commit may become unadoptable and be pushed without review. Restrict flushing to boundaries on the saved branch tip’s first-parent chain, and add a regression test covering a side-branch push followed by renaming and pushing the merge branch.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 8m26s

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-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (576f2d3)

Review identified two medium-severity correctness and reliability issues in post-commit batching.

Medium

  • cmd/roborev/postcommit_batch.go:680 — Rewritten branch history can bypass pending reviews. Ancestor flushing uses the live branch tip whenever the branch still resolves, ignoring the recorded tip. If a branch is reset or recreated onto divergent history, pushing a tag or SHA containing its old pending commits finds no carried range, allowing those commits to be pushed without review. Detect when entry.Tip is no longer on the live branch’s first-parent chain and flush the recorded range separately, preserving both histories when pushed simultaneously.

  • cmd/roborev/postcommit_batch.go:106 — Lock acquisition can block indefinitely. Retries use the command’s unbounded background context, so a stuck or suspended hook holding the lock can indefinitely block later commits and pushes. Use a short timeout for lock acquisition, then log and return; a later hook can recover the range from the unchanged checkpoint.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 9m47s

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-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (3c76878)

Review verdict: Two medium-severity reliability gaps could allow commits to escape review.

Medium

  • Orphaned commits can lose their review checkpoint after divergent historycmd/roborev/postcommit_batch.go:406

    After a branch with pending commits is reset to divergent history, the next post-commit hook replaces its recorded old tip even when handOffPostCommitRange finds no branch containing that tip. A later tag or SHA push of the abandoned tip can therefore push those commits without triggering review.

    Suggested fix: Preserve the old checkpoint as a separate orphaned range until reviewed or flushed, while tracking the rewritten branch separately. Add coverage for reset → new commit → old-tip push.

  • Lock contention can permanently skip immediate commit reviewscmd/roborev/postcommit.go:94

    Every post-commit invocation acquires the batch lock, even when batching is disabled and no batch state exists. If a slow or stuck holder causes a timeout, the hook skips review without recording a checkpoint. Under the default commit-review mode, a later hook reviews only its own HEAD, so the skipped commit is never retried.

    Suggested fix: Bypass the shared lock for non-batched immediate reviews when no state needs draining, or record retryable state before returning after lock failure.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 14m59s

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-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (4d657d4)

The post-commit batching change has two medium-severity correctness issues involving divergent branch histories and pending review ranges.

Medium

  • cmd/roborev/postcommit_batch.go:406propagateEscapedPostCommitRange replaces the recorded tip even when handOffPostCommitRange finds no live first-parent branch to receive it. If a branch is force-moved to divergent history, the old pending range is forgotten and a later tag or SHA push will not flush it.

    • Fix: Have the handoff report whether the old range was transferred or covered. Otherwise, preserve it as a separate orphan/incarnation entry before tracking the branch’s new history.
  • cmd/roborev/postcommit_batch.go:755 — An abandoned recorded tip is considered only when FirstParentDistance returns onChain=false without an error. Unrelated histories return an error, so force-moving a live branch to an orphan history can hide its old pending tip and prevent review when that tip is later pushed by tag or SHA.

    • Fix: Distinguish unrelated/off-chain histories from unresolved or transient Git failures, and include any resolvable recorded tip that is not on the live branch’s first-parent chain.

Reviewers: 2 done | Synthesis: codex, 13s | Total: 10m53s

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-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (cdde231)

One medium-severity correctness issue remains in post-commit batching.

Medium

  • cmd/roborev/postcommit_batch.go:406 — When a branch is reset or force-moved away from a pending tip and later receives another commit, handOffPostCommitRange may find no live branch containing the old tip, yet the caller still replaces the recorded Tip with the new head. This loses the abandoned pending range, allowing a later tag or SHA push of the old tip to bypass the pre-push flush and leave those commits unreviewed.
    • Suggested fix: Preserve the old range as a separate orphaned entry unless handoff confirms coverage. Add a regression test covering reset → new commit → old-tip push.

Reviewers: 2 done | Synthesis: codex, 10s | Total: 8m27s

@wesm
wesm merged commit 158e0fb into main Aug 24, 2026
32 of 33 checks passed
@wesm
wesm deleted the feat/post-commit-batching branch August 24, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants