Repository navigation
fix(pr-pipeline): arm lane pull requests reliably and say on each why it stays unarmed - #9545
Conversation
…ork once enable-auto-merge's GraphQL calls (the listing, discussion reads, arm and disarm) are POSTs, which createGitHub's retryServerErrors never resends. A "fetch failed" TypeError or an ECONNRESET, ETIMEDOUT, UND_ERR_SOCKET or UND_ERR_CONNECT_TIMEOUT cause is now sent once more after 3 s; an HTTP error answer or a GraphQL error is not. A retried arm passes the same expectedHeadOid. Failure lines name the cause's code and message, tokens scrubbed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st up to twice The arm failures came after the verifier's subprocess: the mutation went out on undici's pooled keep-alive socket, which had sat idle past GitHub's keep-alive window. Every request now sends Connection: close, so each one opens its own connection. A POST that still fails on the network is sent at most three times in all, with capped full-jitter waits (4 s, then 8 s). The retry warning names the arm or disarm and its pull request. The remaining catch sites log the cause, and the scrub also covers github_pat_ tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rning The ::error line keeps the scrubbed cause message for diagnosis; the warning follows the shared client's errorLabel convention, since undici can put a header value in a message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r forwarding Review findings: the jitter caps, the GET guard and createGitHub's forwarding of the Connection header had no test that fails when broken, and the github_pat_ scrub had none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
claude-review holds this pull request for a person (trust-gate) at Changed files match hold-paths: claude-lane: claude-review |
|
claude-security-review holds this pull request for a person (trust-gate) at Changed files match hold-paths: claude-lane: claude-security-review |
|
claude-security-review holds this pull request for a person (trust-gate) at Changed files match hold-paths: claude-lane: claude-security-review |
|
claude-review reviewed Incremental review of the three files changed since b9f5483. I found nothing to flag.
|
|
claude-review holds this pull request for a person (trust-gate) at The PR adds a test to .github/actions/check-trusted-trigger/check-trusted-trigger.test.mjs, a file under .github/actions/check-trusted-trigger/**. It is test-only and the action's source is unchanged, but this path is a trust-gate class that needs a person's sign-off. Changed files match hold-paths: claude-lane: claude-review |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it unarmed Under merge.lane-prs native the gate never lists a lanes App pull request, so a refusal enable-auto-merge read left only a log line. A reader run now writes one babysit-merge-risk comment naming the head and reason, edited in place and left alone while unchanged, and adds the human-gated label for a refusal a person must clear: a hold or cost path, a link the lane does not take, a needsHuman verdict, or a risk hold that stood merge.risk-escalation-minutes, as the gate escalates it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/scripts/pr-merge/enable-auto-merge.test.mjs
|
claude-security-review reviewed Incremental security review of the three files changed since b9f5483. I found no security issues. critical-findings: 0
|
…contract (#864) No related issue: follow-up to [ci-workflows#874](melodic-software/ci-workflows#874), which fixed a review-lane failure on [claude-code-plugins#9545](melodic-software/claude-code-plugins#9545). ## Summary Registers `pr-review-cli.yml@b49ac366bb3426039363a6927c41512e44533808` (ci-workflows [v0.65.1](https://github.com/melodic-software/ci-workflows/releases/tag/v0.65.1)), so claude-code-plugins' two review callers can re-pin to the release that passes the review between jobs as an artifact. ## Fix v0.65.1 changes only `pr-review-cli.yml`'s jobs and its tests: the review and the last marker's summary now travel as a checked artifact instead of a job output, which the runner dropped whenever the text contained a registered secret's value. The workflow's `on.workflow_call` inputs, secrets and runner did not change (`git diff d02b8697..b49ac366 -- .github/workflows/pr-review-cli.yml` touches the header comment and `jobs:` only), so the contract copies its v0.65.0 entry forward verbatim. ## Verification - `node --test runner-policy.test.mjs` in `components/runner-policy`: 343 pass, 0 fail. That includes the new copy-forward test, which checks the new entry deep-equals the v0.65.0 entry and that exactly one contract names the new SHA. - `npx --no-install @biomejs/biome check` (2.5.15) on both changed files: clean. ## Related - Same shape as [#856](#856) (v0.65.0) and [#853](#853) (v0.64.2). - Next: the standards sync into claude-code-plugins, then that repository's review-caller re-pin. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…fusal comment A label write that failed after its person comment was never retried: every later run saw the comment unchanged and wrote nothing. When the lanes App's person comment stands and the label is off, the run now reads the pull request's label removals and adds the label back unless a person removed it at or after the comment's last edit. A timeline not read whole adds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y once it succeeds The refusal note's comment POST is not idempotent, so a lost answer could post a second marker comment; only /graphql requests are resent now. A disarm that threw left the pull request armed, yet the note followed and said it was unarmed; the note now runs only after a disarm that succeeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: transient arm failures seen in runs 40051924577 and 40023195885
Summary
pr-merge's enable-auto-merge job logged
#9540 arm: fetch failedin scheduled run 40051924577 and#9489 arm: fetch failedin run 40023195885. Quota was not the cause. Each failure followed a verifier read, which runs as apython3subprocess. The arm mutation then went out on undici's pooled keep-alive connection, which had sat idle past GitHub's keep-alive window. "fetch failed" is undici's TypeError for a network or socket error, and it keeps the cause inerr.cause, which the log dropped. One such error left a ready lane pull request unarmed until the next scheduled run, about an hour later.Fix
Connection: close. Each socket closes after its answer, so no request goes out on an idle pooled connection. A local probe on Node v24.21.0 confirmed that undici forwards the header and that the next request opens a new socket.ECONNRESET,ETIMEDOUT,UND_ERR_SOCKETorUND_ERR_CONNECT_TIMEOUT, or a cause of "other side closed". The waits use full jitter, capped at 4 s and then 8 s. An HTTP error answer (4xx included) and a GraphQL error response come back as aGitHubErrorwith no cause, so neither is retried.::errorline names the PR number and the error's cause code and message. The run moves on to the remaining pull requests and exits 1 at the end.retryServerErrorsin.github/actions/check-trusted-trigger/check-trusted-trigger.mjsresends only GETs, by design ("Only GETs are idempotent"). GETs stay with it, and the script's own wrapperwithRetrycovers its GraphQL POSTs.expectedHeadOid, so it can arm only the head the run read. GitHub's GraphQL reference (pulls) does not say what a secondenablePullRequestAutoMergeon an already-armed pull request returns (UNVERIFIED). There are two possible outcomes, and both are harmless: the call succeeds, or it returns a GraphQL error that the run reports while the pull request stays armed. A redundant disarm behaves the same way.merge.lane-prs: nativethe gate never lists a lanes App pull request. Until now, a refusal from enable-auto-merge was only an Actions log line, and the pull request sat with no visible reason. Now a reader run (the schedule or a hand-started run) writes the refusal on the pull request whenever it leaves the PR unarmed or disarms it for a hold path, a cost path, the gate's verifier verdict, or its risk score.<!-- babysit-merge-risk -->and the shape of itsrisk_comment_body: the marker, a line namingat head <sha>, and the reason in a text fence.work-items:escalationmarker first. These refusals are: a hold or cost path, an issue link the lane does not take, a verdict the gate marksneedsHuman, and a risk hold whose comment has stood unchanged formerge.risk-escalation-minutes(the gate's escalation rule; 60 when unset).issues: writeandpull-requests: write(effectmerge,.github/actions/resolve-config/effect-grants.json).describe, which first scrubs token-shaped text (Bearer …,gh?_…,github_pat_…). The resend::warning::line names the arm or disarm and its pull request, but gives only the error's name and cause code (errorLabel), as the shared client does.Verification
node --testin.github/scripts/pr-merge: 294 tests, all pass, after merging main. Refusal-note cases, with the expected bodies written out by hand:needs-humanlabel.needsHumanverdict is treated as a person's refusal;verify:missingis a lane's and gets the comment only.enable-auto-merge.test.mjs, for the retry change:ECONNRESET read ECONNRESET.Connection: close.nice bash scripts/check-affected.sh --uncommitted: 9 passed, 4 skipped.enablePullRequestAutoMergecall, because arming a real pull request would merge it.Related
N/A
🤖 Generated with Claude Code