Repository navigation
F: merge the task's published PR, not github.pullRequest (#121) - #126
Merged
Merged
Conversation
With a runner block, the guarded merge gate now inspects, merges and polls the task's published pull request: the task's latest opened record in github.baseBranch, which GitHub must show from the task branch, into that base, with its marker, as the only open PR from the branch, and not a draft. github.pullRequest becomes optional with a runner block and blocks merging when it names another PR; without a runner block it is required and used as before. The resolved PR must be the same in every validation pass; admission re-reads the PR record in the transaction that saves the attempt, and the attempt pins its PR so reconciliation and queue polls never re-resolve it. The pre-merge already-fixed check treats every PR the task recorded in the repository as its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation consistently pins the intended PR, fails closed on mismatches, and includes broad race-focused regression coverage.
Review effort: Balanced
Findings: None
What changed in this PR
Targets guarded merges at the runner task’s published PR and pins that PR throughout validation, submission, reconciliation, and queue polling.
Changes:
- Resolves and validates published PR identity, draft state, branch, base, marker, and uniqueness.
- Persists the selected PR on merge attempts and uses it for all later operations.
- Adds comprehensive regression coverage and updates documentation.
| File | Description |
|---|---|
web/server.ts |
Selects published-PR merge mode and validates configuration. |
runner/merge.ts |
Resolves, validates, pins, and reconciles merge targets. |
runner/store.ts |
Persists target PRs and guards admission races. |
runner/review.ts |
Exposes runner-block presence to merge configuration. |
runner/publish.ts |
Reuses the shared opening-marker helper. |
runner/production.ts |
Re-exports shared base-branch validation. |
github/merge.ts |
Adds target-aware inspection, caching, queueing, and merging. |
github/pull-requests.ts |
Exports opening-marker construction. |
github/validate.ts |
Hosts shared base-branch validation. |
test/merge-task-pr.test.ts |
Covers published-PR targeting and race guards. |
test/merge.test.ts |
Updates gateway fixtures for PR identity and draft state. |
test/runner-shutdown.test.ts |
Updates shutdown merge fixtures. |
test/runner-publishing.test.ts |
Updates publishing merge fixtures. |
test/browser/review.spec.ts |
Updates browser merge fixtures. |
README.md |
Documents optional configured PRs with runners. |
docs/implementation/guarded-merge.md |
Documents target selection and pinning. |
docs/implementation/merge-queue.md |
Documents queue target persistence. |
docs/implementation/pull-request-opening.md |
Documents published-PR merge behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… configured PR through the server (#121) Review round 2 on PR #126 (independent agent): - displayStatus showed only the target blocker when no PR could be resolved, hiding approval, task-status and merge-attempt blockers. It now shows the attempt's blocker first, then the target's, then the review's own. - The server's passing of github.pullRequest as the configured PR is now tested end to end, and #113's unplanned-blocker test runs the gate in published mode. - guarded-merge.md: the in-flight update case names the record's refresh field, not a blocker code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Closes #121. Replaces #122, which GitHub closed when merging #110 deleted its base branch
feat/103-publish-pr.Rebase onto main (head
a2c6dba)The two #121 commits were replayed onto
main(after #110, #113, #114 and #111). Changes from the reviewed headcca2dfd:pull-request-opening.md: main's text kept, with the F: merge the task's published PR, not github.pullRequest #121 sentences added. F: publish only ledger-recorded commits (snapshot head may hold others) #113's note that "pointing the gate at the published PR is a follow-up" now says the gate inspects and merges the task's published PR.test/runner-publishing.test.ts: F: publish only ledger-recorded commits (snapshot head may hold others) #113's merge-gate test builds aRemoteMergeStateby hand. It now gives the two new fields,pullRequestanddraft.After the rebase:
npm run typecheckis clean. Locally, 1565 tests passed. The only failures were the Docker container tests, because Docker is not running on this host. CI'sreal-dockerjob is the record for those.Problem
Since #103 the runner publishes its own pull request for each task. The guarded merge gate still inspected, merged and polled only
github.pullRequest. A runner task's merge was therefore always blocked: the configured PR's head is not the task head, and the task's own PR counted as "another PR" in the already-fixed check. It could also target the wrong PR. Merge attempts did not record their PR, and nothing read the draft flag.Change
runner/merge.ts,web/server.ts):openedrecord in the configured repository andgithub.baseBranchwhose opening began last.pull-requestblocker.github.pullRequestis optional with a runner block. If it is set and names another PR, merging is blocked.github.pullRequestis required (the server refuses to start without it) and is used as before.draft) in both modes.Store.beginMergeAttemptre-reads the record in the transaction that saves the attempt. It must still be the opening that began last in its base, with the same number, and no opening or update may be in flight.pullRequestin its JSON data, so no migration is needed. Reconciliation, the queue watermark, queue polls andgh pr mergeuse the attempt's PR and never resolve it again.github.pullRequest.github/merge.ts):pr viewnow also reads and validatesnumberandisDraft. For a published target it also readsbodyandisCrossRepository, and lists the open PRs from the task branch (one page; a full page fails closed).openingMarkermoved togithub/pull-requests.ts, andbaseBranchmoved togithub/validate.ts(re-exported fromrunner/production.ts). The server can now check both without loading the runner.guarded-merge.md(new "Which pull request (F: merge the task's published PR, not github.pullRequest #121)" section),merge-queue.md,pull-request-opening.md, README.Evidence (before the rebase)
Validated head:
cca2dfd.npm run typecheck: clean.merge-task-pr(45, new),merge,runner-shutdown,review,publish,runner-production,cli,runner-publishing,store,gh-env,already-fixedandrunner-start.Review findings (independent review agent, round 1)
Promise.all. A failing list read left the rule-readghprocesses running past the inspection.#assertPublishable(needsrunningorneeds human; refuses during an active merge).No new AGENTS.md rule: every finding is covered by an existing rule or is one-off.
Copilot review, round 1 (head
cca2dfd)No findings, so nothing changed or was declined. CI on
cca2dfd:testandreal-dockerpassed.Copilot review, round 2 (rebased head
a2c6dba)No findings ("Approval recommended"), so nothing changed or was declined. CI on
a2c6dba:testandreal-dockerpassed.Review findings (independent review agent, round 2, full diff after the rebase)
The reviewer found no high or medium defects. It checked how the gate interacts with #110, #111, #113 and #114:
cancelTaskrefuses during an active attempt.Commit
6b29a8a:github.pullRequestas the configured PR.startServertest with a recorded PR #7 andpullRequest: 5expects the mismatch blocker. Removing the passing makes it fail.pull-requestblocker.displayStatusshowed only that blocker. It hid the approval, task and merge-attempt blockers (for example, an attempt saved before #121 that has merged).guarded-merge.mdwrote "(refresh)", which reads like a blocker code.gh pr merge, a person could retarget the PR's base or edit its first line.Tests:
npm run typecheckis clean. The 5 affected files (merge-task-pr,runner-publishing,merge,review,runner-shutdown): 277 passed. The full local suite is slowed by host load (load average about 150), so CI is the full-suite record.Follow-ups: none.
🤖 Generated with Claude Code