Skip to content

perf(pr-verify): skip the pr-merge wake for an armed or held pull request - #13087

Merged
kyle-sexton merged 3 commits into
mainfrom
perf/wake-skip-armed-held
Nov 4, 2026
Merged

kyle-sexton merged 3 commits into
mainfrom
perf/wake-skip-armed-held

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: part of the hub's arm-on-green order (green to first merge-group run is 53% of push-to-merge minutes). Issues are being driven to zero, so none was filed.

Summary

The pr-verify pass wake dispatched a whole pr-merge run for a lanes App PR even when the PR was already armed or held by do-not-merge. In the #12445 AFTER v4 window those wakes cost about 260 calls (#13031, #13032, #13015, #12991): each ran a fresh post-merge check (66-116 calls) and changed nothing.

Fix

The wake's single author read (gh api repos/{repo}/pulls/{n}) now also returns auto_merge != null and whether do-not-merge is set. A lanes App PR that is armed or held gets a notice and no dispatch:

  • ::notice title=pr-verify wake::pr=N verdict=V already armed; nothing woken
  • ::notice title=pr-verify wake::pr=N verdict=V held (do-not-merge); nothing woken

Why skipping is safe:

  • The wake runs the same decide() as the 30-minute sweep, and nothing in it disarms only on a wake (enable-auto-merge.mjs:1464-1576). An armed PR therefore keeps every disarm path through the sweep, as it did before the wake existed.
  • ci-status's pr-contract fails a do-not-merge PR in the merge queue.
  • A failed read, or no lanes App id, still dispatches as before.

Call delta (R34): 0 added. Every skip saves one whole pr-merge run.

Verification

  • node --test .github/scripts/pr-verify/workflow.test.mjs: 54 pass after merging origin/main. The pinned wake test was updated, with armed and held cases added; the builder broke the armed check on purpose and the new test went red.
  • The pr-merge decide and enable-auto-merge suites and reconcile.test.mjs: 862 pass, 0 fail.
  • actionlint, sync-hold-floor.py --check and the fragment checks pass.
  • After merge (R21): the next two pr-verify runs that reach the wake step. Then count the skip notices against pr-merge workflow_dispatch runs on a window after perf(api-budget): count header-less babysit calls and the credit job's calls #13084.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 2 commits November 3, 2026 23:59
…uest

The wake's one author read now also returns whether the pull request is
already armed for auto-merge and whether it carries do-not-merge. A lanes
App pull request in either state gets a notice and no pr-merge dispatch:
the scheduled sweep runs the same decide() on it, disarms included, and
ci-status holds do-not-merge in the merge queue, so the wake only spent a
pr-merge run.

Person pull requests stay unwoken: the token broker mints for a verdict
wake only when the lanes App opened the pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 4, 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-04T05:05:15.319110Z 06f6eae PR opened
ℹ️ 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.

Reading the author through process substitution took read's status,
not gh's: a multi-line HTTP error body made read succeed on its first
line, and the wake logged "not a lanes App pull request" instead of
dispatching as a failed read should. The answer is captured first, so
a failed gh call dispatches as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

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

Security review: PR #13087, incremental (06f6eae → e1ab0dc)

I found no security issues in this push.

Scope. This is an incremental review. I read every hunk of both files on the incremental list: .github/workflows/pr-verify.yml and .github/scripts/pr-verify/workflow.test.mjs. pr-verify.yml matches a trusted-path glob, so I read its whole change and the Wake pr-merge for this pull request step it sits in (pr-verify.yml#L5685-L5743).

What changed. The wake step used to read the pull request with read -r … < <(gh api …). It now runs answer=$(gh api …) && read -r … <<<"$answer" (L5722-L5725). With the old form, the if saw only the exit status of read. If gh printed an error body (for example an HTML 502 page) and then failed, read took that body's first line as the author id. The step then reported "not a lanes App pull request" and skipped the wake. With the new form, a gh failure fails the if condition, so the step falls through to the pr-merge.yml dispatch. That is the "failed read dispatches anyway" behaviour the comment describes. The test's gh stand-in gains GH_ERROR_BODY, and a new assertion checks that a failed read with a multi-line error body still dispatches.

Checks:

  • Who is trusted. No widening. The author check "$id $type" != "$LANES_APP_ID Bot" is unchanged. The fix only stops a failed read from being taken as an answer.
  • Token scope. Unchanged. The step still uses GH_TOKEN: ${{ github.token }} (L5689), makes the same gh api read and the same gh workflow run call, and no permissions: block changed.
  • Merge holds. None are weakened. On a failed read the step now dispatches pr-merge, whose own decide() applies every hold. Before this push it could skip the wake. The step never arms or merges anything itself.
  • Injection. The --jq filter is a fixed string, and $pr was checked earlier against ^[1-9][0-9]{0,15}$ (L5711). $answer is only split by read and compared against literals, never run. Error-body text never reaches a command.
  • Test file. The GH_ERROR_BODY stand-in runs only in the local test harness. printf '%s' quotes the value, so there is no security surface.

Prior findings. My one earlier post was a no-findings summary for 06f6eae. Nothing is repeated, and this is round 0 of a cap of 3, so the cap does not apply. I found no embedded instructions in the PR context.

Spend. On a failed author read this push can dispatch a pr-merge run that the old code would have skipped. That spends Actions minutes and API calls only. Nothing new is billed.

critical-findings: 0

@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 06f6eae6143387ad48e4d3ed780cdfb07615108a; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread .github/workflows/pr-verify.yml Resolved
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

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

Code review (incremental, 06f6eae → e1ab0dc)

I read both files in the incremental scope in full: .github/workflows/pr-verify.yml and .github/scripts/pr-verify/workflow.test.mjs. I found nothing new.

Prior finding resolved. The earlier finding was that a failed read no longer dispatched (thread). This push fixes it. The wake step now captures the output first with answer=$(gh api …), then splits it with read -r id type armed held <<<"$answer" (pr-verify.yml#L5722-L5725). Because both commands are joined with &&, the if condition gets gh's exit status again. On an HTTP error, gh prints the error body and exits 1, so the condition fails and the step falls through to the dispatch. That matches the comment above the step and the README. The step runs under set -euo pipefail, but errexit does not apply inside an if condition, so a failing gh can't abort the step either.

Test coverage. The gh stub now prints GH_ERROR_BODY and exits 1. The new assertion feeds it a three-line HTML 502 body with no GH_AUTHOR and expects [WAKE_READ, WAKE_DISPATCH]. Under the old read … < <(gh api …) form, read would have taken <html> as id, logged "not a lanes App pull request" and recorded only WAKE_READ. So this test would fail on a regression to the bug.

Other notes:

  • No closing issue. The PR body says none was filed (Refs pr-pipeline: wake pr-merge per PR on pr-verify completion and make the cron a backstop #12445), so there are no acceptance criteria to check.
  • I did not run the author's verification (node --test …); those results are claimed by the author and unverified.
  • Spend: none. The change only fixes the fallback path, which dispatches pr-merge when the read fails.
  • Prior findings: 1 earlier round of 3. The one inline finding is addressed by this push; I'm not reposting anything.

@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit cb26b84 Nov 4, 2026
51 checks passed
@kyle-sexton
kyle-sexton deleted the perf/wake-skip-armed-held branch November 4, 2026 05: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.

1 participant