Repository navigation
Defer review of stacked pull requests until they land on the default branch - #45
Merged
Merged
Conversation
…branch A pull request whose base is another branch (stacked on an earlier, still-open PR) is no longer reviewed when opened, pushed to, or marked ready. It is reviewed when it is retargeted onto the default branch, which GitHub does when the PR beneath it merges. Channels can opt out with skipStacked: false for repositories that review into non-default branches.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
GitHub moves a stacked PR onto the default branch only when the PR beneath it merges and that PR's branch is deleted, so the docs now say so and name the recovery: enable "Automatically delete head branches" or change the base by hand. A payload with no default_branch no longer admits an edited base change as a retarget; before, any base change passed, including a move onto a stack. The prompt states only that the base changed, naming the previous base inside the untrusted block, and a deferred stacked PR now leaves a log line instead of a silent 204. Fixture branches use demo-contributor names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
|
Review follow-up in 9842485. Fixed
Left open
Verified: github receiver and webhook route packages pass under 🤖 Generated with Claude Code |
An unset skipStacked now means off, so existing Channels keep reviewing into every base branch after upgrade; the github wizard writes skipStacked: true for new Channels. A base change that keeps the base commit (a renamed default branch) is no longer a retarget, and a retarget is admitted only on a Channel whose events list "opened". The repositories allowlist runs before the stacked check, so the deferral log only names repositories the Channel reviews. The retarget prompt carries the previous base commit, scoping the review to the PR's own commits when the lower PR was squash- or rebase-merged, and tells the agent not to re-review a head commit an earlier review already covered. Translate decides the retarget once and passes it to renderPrompt.
A deferred stacked PR could wait forever for a review with nothing on the PR saying so. Each deferred delivery now leaves a queued check run naming the base it waits on and how to end the wait; Claim adopts that run when the review starts. Queued, not neutral, so a lost retarget cannot let an unreviewed PR satisfy a required check. Admission no longer strands PRs: - a retarget is admitted for a Channel listing any review action, not only "opened"; - ready_for_review is admitted on skipStacked Channels that skip drafts, since a retarget that arrived as a draft was dropped; - an unchanged base commit no longer marks a rename, which also matched a lower PR merged by fast-forward. The retarget prompt no longer asserts the lower PR merged: it narrows the review only when the agent confirms the merge, else reviews the whole diff. oap channel create now points at "Automatically delete head branches", which GitHub's retarget depends on. Deferral reporting is a new optional WebhookReceiver capability, WebhookDeferralReporter, called by webd's webhook route for 204 deliveries; the e2e harness points it at the fake GitHub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Review: ways a stacked PR could wait forever for a reviewA review of this branch looked for ways a deferred stacked PR ends up never reviewed. It found seven, plus one correctness bug in the retarget prompt. 909af5c fixes all of them.
Design notes for review
Not verifiedTwo findings depend on GitHub behaviour I couldn't confirm from the docs, and I didn't test them live:
The fixes for both hold either way. Verification
🤖 Generated with Claude Code |
Loop.fail emits the RunnerTerminal lifecycle event before calling WriteFailed. The operator folds that event into phase=Failed and FinishedAt but writes neither FailureReason nor the Failed condition, and WriteFailed skipped any session already in a terminal phase. When the operator's reconcile landed between the two, the session ended Failed with no reason, silently. This made TestIdentityChoice_NonInteractive flaky in CI (on main as well), and widening the window with a sleep made it fail every time. WriteFailed now fills in only the missing parts of a Failed phase's record, so a reason another writer already recorded is kept, and a Succeeded session is left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
samkim
approved these changes
Nov 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
A stack of about ten dependent pull requests was marked ready for review at once. Each one is based on the branch of the PR beneath it, and reviewbot started a review for every one of them in the same second. That burst ran the cluster out of room, so most of the reviews failed. And the ones that did run reviewed each lower PR's changes again inside every PR stacked above it.
Summary of changes
skipStacked, controls this. It is opt-in, so existing channels keep reviewing PRs into every branch after upgrade. Channels created withoap channel createturn it on. Leave it off for repositories that genuinely review into non-default branches, such as release branches: those PRs look exactly like stacks to this check.Alternatives considered
Provenance
Ship gate
mage test:unitmage test:integrationmage test:e2eAll three pass. The first e2e run had one failure, in an unrelated identity scenario. It passed when re-run on its own and on a full re-run, and the machine was heavily loaded at the time.
Regeneration
config/**changes. n/amage fmt:checkcouldn't run locally because of a package-manager signature error in its markdown/TypeScript step.Coverage
Notes for reviewers
Automatic retargeting only happens when the merged PR's branch is deleted, through the repository's "Automatically delete head branches" setting or a stacking tool. Without that, stacked PRs keep pointing at the merged branch and wait until someone retargets them.