Repository navigation
feat: add batch-backport label to backport merged PRs together - #453
Conversation
Merged PRs labeled `batch-backport` wait until no open PR carries the label (or two hours after the oldest of them merged), then go out as one backport PR per target branch. The queue lives in GitHub labels; trop only keeps a deadline timer, re-armed by a search on startup. Members that do not apply on top of the earlier ones are left out and flagged for a manual backport while the rest of the batch still goes out; the sandbox job applies members one after another (protocol v2) and the backstop still checks the result. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RMRMPAJGJ2vLN5aDeYD32
There was a problem hiding this comment.
Findings marked 🟡 are optional suggestions and need no follow-up push.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🔴
src/utils.ts— One batch member's failed preparation marks every PR in the batchneeds-manual-bp/*, not just the member that failed. When a unit has no 'all' attempt and its squash fetch fails with a non-PatchError, the loop at src/utils.ts:1529 throwsSandboxInfraError(..., false). That non-retryable error reaches the conflict branch, which comments on every top and callsflagForManualBackport(context, prs, ...)for all PRs. Fix: drop just that unit (comment on it, flag it, fail its check run), as a sandbox conflict does, and let the rest of the batch go out. Keep throwing for a retryable SandboxInfraError.Why this was flagged
Trigger: a batched PR with 240 or more commits, or with only merge commits, gets null from getAllCommitsAttempt. Its getSquashAttempt then fails with any error that is not a PatchError, for example a 404 or 422 from fetchBackportCommit. In backportUnitsImpl (src/utils.ts:1520-1535) the batch path throws
new SandboxInfraError(Could not fetch the squash commit(s) of #N, false). Becauseretryableis false, the error handler skips the infra branch at src/utils.ts:1244 and runs the conflict path. That path posts "I was unable to backport this PR ... cleanly" on every top and calls flagForManualBackport on allprs, so every PR loses itstarget/<branch>label and getsneeds-manual-bp/<branch>. Without batching, only the PR with the failure is flagged and the others are backported. The PR description says a member that does not apply is dropped while the rest still go out. This path breaks that promise because the unit is never handed to the sandbox, so it can't be dropped the way a sandbox conflict is.Verification: The code is in backportUnitsImpl at src/utils.ts:949-966, which throws a non-retryable SandboxInfraError when a batch member has no 'all' attempt and its squash fetch fails, such as ECONNRESET in fetchCommitAs (src/utils.ts:220). Because
retryableis false, the infra branch at :1244 is skipped. The conflict path then callsflagForManualBackport(context, prs, targetBranch)(:1333) with all PRs of the batch.
- A batch member whose commits don't apply and whose squash commit can't be fetched for good is left out like a conflict, instead of failing the whole batch; GitHub not answering still retries it. - A batch an internal error stopped is retried on its own timer (up to three times in a row), and its PRs no longer make the PRs waiting since overdue, so a new merge still waits for open labeled PRs. - A failure while handling a merge or a PR leaving the batch evaluates the batch again a few minutes later. - The startup rescan carries on past an installation that fails, and sorts its search by recency so waiting PRs stay under the 1000-result cap. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RMRMPAJGJ2vLN5aDeYD32
|
Re the review-body finding about a batch unit with no all-commits attempt whose squash fetch fails (src/utils.ts): fixed in 4a4db0e. That unit is now left out the way a sandbox conflict leaves one out: a comment on its top PR, Generated by Claude Code |
A batch backport runs once per target branch, but its retryable failures were recorded per PR, so a success on one branch erased the failure on another: the retry then found nothing, and the PR's old merge time made later merges skip waiting for open labeled PRs. Failures, their retry count and the deadline now go by PR and branch, a retry backports only the branches that failed, and failures of branches no longer queued are forgotten. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RMRMPAJGJ2vLN5aDeYD32
Requested by Samuel Attard · Slack thread
This adds a
batch-backportlabel so related PRs can be backported together instead of one backport each. A merged PR with the label waits until no open PR carries it (or two hours after the oldest waiting PR merged, or until/trop run backport), and then trop opens one backport PR per target branch containing every waiting PR targeting it, in merge order, with oneBackport of #Nline per original, combined notes, every author asked for review and the highest semver label. The queue lives in the labels themselves so a restart can't lose it, trop only keeps the deadline timer and re-arms it on startup, and a member that doesn't apply on top of the earlier ones is dropped toneeds-manual-bp/*while the rest still go out, which needed the sandbox job to apply members one after another (protocol bumped to v2, diffs only, backstop unchanged); stacks keep failing as a unit and docs/usage.md covers the details.Nothing here has run against real GitHub yet, only the specs and the local sandbox runner.
Generated by Claude Code