Skip to content

feat(pr-pipeline): dequeue a pull request when the lanes App adds needs-human - #12608

Merged
kyle-sexton merged 6 commits into
mainfrom
feat/pr-merge-dequeue-on-hold-label
Nov 3, 2026
Merged

kyle-sexton merged 6 commits into
mainfrom
feat/pr-merge-dequeue-on-hold-label

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Closes #12449

Summary

When the lanes App adds the human-gated label (needs-human) to a pull request that is in the merge queue, it now takes that PR out of the queue at the same call site. Before this, the PR's merge group still ran, and ci-status failed it on the label. verify-notes.mjs leaveQueue already did this for pr-verify-failed.

Why this design: of the 22 hold-label merge-group failures in the 7-day BEFORE baseline, 9 had the label added after the PR entered the queue (design check):

  • 5 were needs-human, which the lanes App added.
  • 4 were pr-verify-failed, whose writer already dequeues.

The earlier version of this PR woke pr-merge on a person's labeled event. That design catches none of the 5, because a bot-sent event is skipped and the token broker refuses a bot actor. So the dequeue now sits in the writers themselves. The merge-group ci-status already re-reads hold labels in the queue (.github/workflows/pr-require-checks.yml). That check stays the backstop, and the dequeue spares the failed group.

The label set follows the owner's decision on #12446 (Q3), quoted from the issue comment:

Question (verbatim): "Q3 (revised): which label goes on routine stuck-agent PRs in #12446?" with recommendation (a): "match each label to its condition. Vetoes keep do-not-merge. Lane stops and stuck review findings get needs-session, which already exists. needs-human stays only for intent, design, security and cost. human-merge-hold is removed."

Owner's answer (verbatim): "Go with recommendations."

So needs-human is the hold these writers add, resolved by humanGatedLabelFrom in .github/scripts/pr-unblock/address-feedback.mjs. needs-session is not a hold, and human-merge-hold is gone.

Fix

  • address-feedback.mjs:
    • New leaveQueueOnHold: after a hold-label add, it makes one QUEUE_ENTRY_QUERY read. If the PR is queued, it sends one DEQUEUE_MUTATION.
    • It prints one line per add: ::notice title=hold-label-dequeue::pr=<n> label=<l> outcome=<dequeued|not-queued|refused:<why>>.
    • A refused read or dequeue warns and never fails the label add.
    • QUEUE_ENTRY_QUERY and DEQUEUE_MUTATION are exported, so there is one copy of each.
  • Writers that add the human-gated label with the lanes App token, all wired:
    • report-escalation.sh, hold route, right after it adds the label. It loads both queries from address-feedback.mjs, which the base-script copy carries beside it, and sends them with gh api graphql.
    • enable-auto-merge.mjs, the refusal note: both where it adds the label and where it puts it back.
    • verify-notes.mjs already dequeues its own adds (leaveQueue), and is unchanged.
  • Writers left out, because they use the job's GITHUB_TOKEN, not the lanes App token:
    • security-outside-actor/flag.mjs
    • the check-signed-commits action
  • pr-merge.yml is unchanged from main: the labeled wake, its job condition and its concurrency group are gone.

Verification

  • node --test on .github/scripts/pr-merge/enable-auto-merge.test.mjs, workflow.test.mjs and clear-escalations.test.mjs, and on .github/scripts/pr-unblock/address-feedback.test.mjs and verify-notes.test.mjs: 646 tests, 0 failed. .github/scripts/pr-unblock/report-escalation.test.sh: 1147 ok, exit 0. shellcheck is clean on both shell files.
  • New cases, in both enable-auto-merge and report-escalation:
    • A label add on a queued PR does one read, then dequeues it.
    • A label add on a PR that is not queued does 1 read and no write.
    • A refused dequeue warns, prints refused:dequeue-failed, and the label add and the escalation comment still stand.
    • No label add (a lane refusal, a fail route, the label already on) does no queue read.
  • I broke the dequeue in leaveQueueOnHold on purpose, and the dequeue tests failed (2 of 2).
  • scripts/check-affected.sh --base origin/main: 11 passed, 4 skipped. No plugin changed, so no changelog fragment is required.
  • Call delta: one extra GraphQL read per needs-human add by the lanes App, plus at most one write (the dequeue) when the PR is queued. Every other path makes no new call.
  • AFTER measure: count hold-label-dequeue notices with outcome=dequeued, against hold-label merge-group failures where the label was added after queue entry.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01VcupomBPg7kuobwLoNpxEW

kyle-sexton and others added 2 commits November 2, 2026 21:20
pr-merge.yml wakes on `labeled` too. Only a hold label a person added runs
work, and only the enable-auto-merge job's: the label set is
enable-auto-merge.mjs HOLD_LABELS plus the tracker's human-gated label,
checked by workflow.test.mjs. Any other label, and any bot-sent event,
skips every job.

The labeled run reads that one pull request live. When a hold label still
stands, it takes the PR out of the merge queue with the address-feedback
dequeue mutation, now exported, and disarms a lanes App PR that is still
armed. It prints one pr-merge-hold-dequeue notice naming the outcome. A
labeled run is never superseded, and it runs in a concurrency group of its
own, so it does not wait behind a scheduled run's verdict reads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupomBPg7kuobwLoNpxEW
Resolves the overlap with the PR-scoped verdict wake (inputs.pr): the
enable-auto-merge EVENT_PR now carries both, the merge job's condition
keeps both skips, and evalIf takes inputs alongside contains and fromJSON.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupomBPg7kuobwLoNpxEW
@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Nov 3, 2026
kyle-sexton and others added 2 commits November 2, 2026 21:58
…ds-human

Drops the person-only `labeled` wake from pr-merge.yml. Instead, the lanes
App's own writers of the human-gated label now dequeue the PR they label,
when it sits in the merge queue:

- report-escalation.sh on the hold route
- enable-auto-merge.mjs refusal notes, both where they add the label and
  where they put it back

This is the same step verify-notes.mjs leaveQueue takes for
pr-verify-failed.

address-feedback.mjs leaveQueueOnHold does one QUEUE_ENTRY_QUERY read and,
when the PR is queued, one DEQUEUE_MUTATION, and prints one
hold-label-dequeue notice. A refused read or dequeue warns and never fails
the label add. report-escalation.sh loads both queries from
address-feedback.mjs, so there is one copy of each.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupomBPg7kuobwLoNpxEW
@kyle-sexton kyle-sexton changed the title feat(pr-merge): dequeue a pull request when a person adds a hold label feat(pr-pipeline): dequeue a pull request when the lanes App adds needs-human Nov 3, 2026
@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Nov 3, 2026
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 3, 2026 03:09
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T03:11:52.666469Z 3931880 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed e500cf25dd4fc4099a927b26a10fa3318f6439ba: read 1 of 1 changed file(s); 0 finding(s), 0 as inline comments.

Security review: PR #12608, incremental since 3931880

No security issues found.

critical-findings: 0

Scope: This was an incremental review. The only changed file is .github/scripts/pr-merge/enable-auto-merge.test.mjs, and I read its single hunk in full. It is a test file, and it does not match any listed trusted path; only its sibling enable-auto-merge.mjs does. Every other file in the PR is unchanged since my last review of 3931880, which found no issues.

What changed: The push adds one test, "a refused pull request whose label the lane puts back while queued is dequeued". It checks that when the refusal-note path puts needs-human back on a PR that is in the merge queue, two things happen:

  • the label is added (POST /issues/1/labels);
  • the PR is dequeued, and the run prints a dequeued hold notice.

Why it is not a security concern:

  • The test runs against mocks, so no production code path, token request, workflow or trust control changed.
  • It covers the hold label being put back, which is the path that keeps a hold in place, so nothing here weakens a merge hold.

Untrusted text: Nothing in the PR context tries to steer the review.

Owner threat model: This push weakens, narrows or removes no named friction hold.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 3931880eb9d6440be1a8087db454fe06d444dcbb; the summary is in this lane's pull request comment.

claude-lane: claude-review

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed e500cf25dd4fc4099a927b26a10fa3318f6439ba: read 1 of 1 changed file(s); 1 finding(s), 0 as inline comments.

Code review (incremental, 3931880 → e500cf2): dequeue on a lanes-App needs-human add (#12608)

Scope: I read the one changed file, .github/scripts/pr-merge/enable-auto-merge.test.mjs, which adds one test. I read its whole change.

Last round's inline finding is resolved

The new test, a refused pull request whose label the lane puts back while queued is dequeued (enable-auto-merge.test.mjs#L4081-L4089), uses the same setup as the existing relabel case at L2236-L2241: the lane's refusal comment and its hold note are present, no label is on, and no unlabel event exists. The only difference is queued: true.

  • Its notes assertion allows only the label POST. That forces the writes.body === null branch at enable-auto-merge.mjs#L1912-L1922.
  • Its writes and dequeued notice assertions then depend on the leaveQueue call at L1920.

So deleting that call now fails a test, which is what the finding asked for. I traced this by reading the code; I did not run the tests.

Findings

  1. Advisory, restated from the last round (it has no line anchor, so it is repeated each round): closing issue pr-pipeline: read merge-queue holds once and dequeue a PR when a hold label is added #12449, criterion 2. The criterion reads: "A hold label added to a queued PR removes it from the queue before its group runs."
    • What the PR covers: needs-human added by the lanes App (report-escalation.sh#L368, enable-auto-merge.mjs#L1920, L1929), and pr-verify-failed (verify-notes, which already dequeued).
    • What it leaves out: a person adding do-not-merge or needs-human, security-outside-actor/flag.mjs, and the check-signed-commits action. None of these dequeue.
    • Why the gap matters: the issue's design-check comment narrowed the criterion only to labels added after queue entry. It did not narrow it to particular writers, and nothing in the PR context has changed that since the last round.
    • What to do: before merging, either have the owner narrow criterion 2 on pr-pipeline: read merge-queue holds once and dequeue a PR when a hold label is added #12449 or open a follow-up for the remaining writers. Otherwise closing the issue records the criterion as met when it isn't.

Other notes

  • No settled thread was re-raised. The owner's reply on the L1920 thread says the fix is in this commit, and I checked that the fix holds, as described above.
  • Round count: 1 earlier round with findings, cap 3; the cap did not apply.
  • No plugin manifest changed. No embedded instructions were found in the PR context, settled threads or prior findings.

@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 847488d Nov 3, 2026
47 checks passed
@kyle-sexton
kyle-sexton deleted the feat/pr-merge-dequeue-on-hold-label branch November 3, 2026 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pr-pipeline: read merge-queue holds once and dequeue a PR when a hold label is added

1 participant