Skip to content

pr-pipeline: converge own-actor merge holds on the do-not-merge label #12446

Closed
4 of 4 issues completed
Closed

Description

@kyle-sexton

Refs: #12284

Owner: Automation Lanes.

Summary

ci-status enforces holds in seven places. docs/contracts/loop-lane/README.md:460 calls do-not-merge "the only cross-lane hold". A sample of failed pr-require-checks runs from 2026-11-01 shows holds, not code, behind 17 of 24 red PR runs and 10 of 12 red merge groups. Source: audit comment, gates #6, #7, #8 and #17.

Holds against our own actors that do the same job as do-not-merge (all at cb8429f):

  • human-merge-hold: .github/workflows/pr-require-checks.yml:5686.
  • The lanes App body hold or member veto, read by the verdict reader inside ci-status: :6247. This was 6 of 12 sampled red merge groups. The code comment at :4029-4051 quotes about 860 calls and 6-11 min for a cold read in the queue.
  • The pr-unblock failure commit status: :6186; docs/conventions/pr-pipeline/README.md:2863-2869. This was 6 of 24 sampled red PR runs. A cleared head waits for the half-hourly reconcile re-run.
  • needs-human used as a general park by pr-unblock escalation routes, outside cost and critical security.

Proposal:

  • Each writer adds do-not-merge, and ci-status reads that label alone for these cases.
  • Keep the person-only holds for critical security, cost and trust paths: :5122, :5445 for review-lane holds, and :5551.

Done when

  • human-merge-hold, the lanes App body or veto hold and the pr-unblock stop status each apply do-not-merge, and their separate ci-status steps are removed.
  • pr-unblock escalation routes other than CRITICAL security or cost stop adding needs-human.
  • The review-lane critical-security and cost holds, and the trusted-person lift checks, are unchanged.
  • loop-lane/README.md:460 and pr-pipeline/README.md "Review-lane holds" describe the result.
  • Red ci-status runs caused by holds are re-sampled and reported here.

Activity

  1. added
    priority: highSignificant impact, or blocks an imminent release; staff this cycle.
    on Nov 2, 2026
  2. github-actions commented on Nov 2, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: structural
    Basis: automated intake triage; the lanes App applied this class.

    Size: Large
    Complexity: High
    Uncertainty: Knowable
    Flags: none
    Blocked by: none

    Understood: make do-not-merge the single hold that ci-status reads for our own actors' holds (human-merge-hold, the lanes App body/veto hold, the pr-unblock failure status). Stop pr-unblock routes from parking on needs-human outside cost and critical security. Keep the person-only security, cost and trust-path holds unchanged, update the two docs, and re-sample red runs.

    This touches several ci-status steps, the writers and docs, so it likely needs splitting. The last done-when item (re-sampling red runs over time) needs a later measurement.

    Related, not a duplicate: #12449 (reading merge-queue holds once and dequeuing on a hold label). Parent audit: #12284.

    Automated triage, applied by the lanes App.

  3. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    work-class: structuralRefactors, migrations, contract changes; cross-cutting and hard to reverse.
    on Nov 2, 2026
  4. melodic-automation-lanes commented on Nov 2, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane yet: Size large: the split into sub-issues at intake is pending (owner Q93).

  5. melodic-automation-lanes commented on Nov 2, 2026

    @melodic-automation-lanes
    Contributor

    Split at intake (owner Q93). This issue is Size Large, so its done-when is split into 4 sub-issues, each holding its task-list lines as written.

    Automated split by the lanes App. Admission waits on this issue while a sub-issue is open; work-unblock closes it as completed once every sub-issue is closed as completed by a merge or a listed human and its done-when is covered.

  6. kyle-sexton commented on Nov 2, 2026

    @kyle-sexton
    MemberAuthor

    Status: one builder PR (Automation Lanes). Written by the issue-triage session.

    A sub-issue exists here only when it ships as its own PR. So size-split parts 1, 3 and 4 are folded back into this issue and closed as carried:

    Part 2, #12452, stays a sub-issue. It changes only .github/scripts/pr-unblock/escalation-routes.json, ships and verifies on its own in the issue lane, and carries done-when line 2. It is narrowed to the own-actor routes: the intent and design question routes keep needs-human.

    This issue is the PR unit for lines 1, 3 and 4, with line 5 after merge. Line 1 removes ci-status steps in .github/workflows/pr-require-checks.yml (about :5677-5696, :6187, :6247), which the issue lane cannot push, so the Automation Lanes session builds it by hand. It moves to status: confirmed.

    #12449 (dequeue on a hold label) is now blocked by this issue, because both remove the same ci-status hold steps.

  7. added
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Nov 2, 2026
  8. kyle-sexton commented on Nov 2, 2026

    @kyle-sexton
    MemberAuthor

    Status: part 2 folded in too; builder notes. Written by the issue-triage session.

    #12452 (part 2) is folded into this issue as well. Its criterion is done-when line 2, word for word. The issue-lane run on it (41504526015) stopped on design questions, and those questions sit inside this issue's one change, so the Automation Lanes builder settles them together:

    • Hold label. Own-actor routes apply do-not-merge in place of needs-human, with the reason marker in the same write. needs-human stays on the intent and design question routes and on CRITICAL security or cost routes.
    • Resume path. feedback-dispatch.mjs:23 and dispatch.mjs:75 skip any PR carrying do-not-merge. A needs-human escalation, by contrast, lets address-feedback resume when a trusted person answers on the thread (gate-source.mjs; feedback-dispatch.mjs:33-35). The change must keep a resume path for the rerouted holds.
    • Mixed reasons. feedback-round-cap, feedback-uncertain and feedback-disputed in .github/scripts/pr-unblock/escalation-routes.json each mix a CRITICAL or unrated security finding with other causes. Splitting them needs new reasons emitted by adjudicate-feedback.mjs and address-feedback.mjs, or a stated rule for which reasons count as security. docs/contracts/loop-lane/README.md:327 (intent and design findings go to the human-gated label) stays as it is.
  9. kyle-sexton commented on Nov 2, 2026

    @kyle-sexton
    MemberAuthor

    Owner decision (Q3): match each hold label to the PR's condition. Posted by the issue-triage session. The question was asked of the owner in that session on 2026-11-02, and the owner answered there.

    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."

    What this changes in this issue's direction, for the Automation Lanes builder:

    • Explicit vetoes (the lanes App body hold, a member veto) keep do-not-merge, which the repo describes as "Hard merge gate: do not merge while applied."
    • human-merge-hold is removed, not moved to another label. It is a person-approval hold against our own actors.
    • The pr-unblock stop status and the review-escalation routes (round cap, uncertain, disputed) put needs-session on, not needs-human or do-not-merge. No hold label is needed to block the merge: the main-branch ruleset already sets required_review_thread_resolution: true, so an open thread blocks it. needs-session only routes the PR to a session.
    • Restart on an answer: a session's answer on the thread must restart address-feedback under needs-session, as it does today under the human-gated label (.github/scripts/pr-unblock/feedback-dispatch.mjs:33-35, gate-source.mjs).
    • needs-human stays for intent, design, CRITICAL security and cost findings only (docs/contracts/loop-lane/README.md:326-328).
    • Done-when line 1 ("each apply do-not-merge") and line 2 are read through this decision: the stop status and the escalation routes apply needs-session instead.
  10. self-assigned this
    on Nov 3, 2026
  11. kyle-sexton commented on Nov 3, 2026

    @kyle-sexton
    MemberAuthor
    No description provided.
  12. kyle-sexton commented on Nov 3, 2026

    @kyle-sexton
    MemberAuthor

    Close-out check for #12446: three of the five items are met, so this stays open. Read at origin/main de80b21.

    Item 1: partial. human-merge-hold is gone (only changelog mentions remain). The lanes App body hold applies do-not-merge (report-outcome.mjs:185, :2364). The pr-unblock stop status step is gone. A member's veto comment does not apply the label: merge-holds.mjs:1649 says it "still holds, since nothing labels it". The step at pr-require-checks.yml:6140 stays, because it also carries the kill switch, hold-path and verifier holds, and it still runs the verdict reader (:6428). Its name still says "its body or a member holds", and :6509 still says "no body hold". pr-feedback-dispatch.yml still re-runs ci-status for a status it no longer reads (:575, :581).

    Item 2: met. escalation-routes.json:19,21,29 carry needs-session. The nine hold routes are security, intent, design and dependency (:32-37, :41, :42, :45). A session's answer restarting address-feedback under needs-session was not observed in a run.

    Item 3: met. The cost-hold, human-gated and hold-lift steps have identical non-comment bodies before #12522 and now (pr-require-checks.yml:5169, :5492, :5598).

    Item 4: partial. loop-lane/README.md:460-462 and the pr-pipeline "Review-lane holds" section describe the result. loop-lane/README.md:470-471 still says a comment is never a hold and no gate reads them, which contradicts :461-462. pr-pipeline/README.md:3952 still says ci-status "enforces holds in seven places".

    Item 5: met by this comment.

    Window: failed pr-require-checks runs created after 2026-11-03T01:46:51Z, listed at 04:58:48Z (3 h 12 min). Every failed run was read: 55 pull_request (of 214) and 22 merge_group (of 78). Method as in the #12284 audit, reading failed steps and ci-status annotations. A run can count in more than one row, as in the audit. The audit covered a full day, this about 3 hours, and this window includes a lanes kill-switch stretch and an API rate-limit stretch.

    PR runs 2026-11-01 (of 24) Now (of 55)
    Any hold 17 (71%) 14 (25%)
    do-not-merge 8 14
    pr-unblock status 6 0
    needs-human 3 0
    Lane failed 7 18 (plus 1 review lane failed)
    Review lane still running 2 11
    Review lanes skipped, kill switch on not a row 10
    Superseded by a full run in flight not a row 6
    API rate limit not a row 1
    Merge groups 2026-11-01 (of 12) Now (of 22)
    do-not-merge 1 0
    pr-unblock status 1 0
    :6140 step: body hold or veto by cause 6 by step 0
    :6140 step: verdict reader, no artifact listing not separated 11
    :6140 step: verdict reader refusal not separated 1
    :6140 step: lanes kill switch not a row 1
    pr-verify-failed, label after enqueue 4 (hold label after enqueue) 3
    pr-verify-failed, label on before enqueue not a row 6

    The holds this issue removed caused no red run in this window: 0 pr-unblock status, 0 human-merge-hold, 0 body hold, 0 veto. On pull requests, holds fell from 71% to 25% of red runs, and all 14 are the intended do-not-merge label.

    Merge groups did not improve. By the audit's step classification, every one of the 22 red groups failed in a hold step, against 10 of 12. The red rate was 22 of 78 (28%), against 24 of 363 (6.6%) on 2026-11-01, from 7 PRs. The audit's "body hold or member veto" row was counted by step: its two example runs (41348597754, 41324239904) are verdict-reader refusals, so removing body-hold reading could not lower it.

    Separate from this issue: 11 groups fail at pr-require-checks.yml:6444 with "the verdict reader read no artifact listing" (first: 41573330717). The first was 18 minutes after #12558 turned on FAIL index mode. #12717 is open for it.

    Agent choice on the veto, not an owner answer. A member's veto comment stays read in the merge-holds step and adds no labeling write. Basis: 0 of 22 red groups came from a veto; a member can already apply do-not-merge directly, and #12522's body records the comment read as how it built to Q3; a lanes App write on each veto comment would add a mechanism with no measured need. This reads done-when 1 through the owner's Q3 answer, as #12522 did.

    What remains: rename the :6140 step and its verdict text, drop the rerun-ci-status re-run for pr-unblock statuses, and fix loop-lane/README.md:470-471 and pr-pipeline/README.md:3952. This session is taking that.

  13. kyle-sexton commented on Nov 3, 2026

    @kyle-sexton
    MemberAuthor

    Re-sample, window A (2026-11-03 01:46:51Z to 08:51Z): holds behind 4 of 25 red PR runs and 11 of 12 red merge groups, vs 17 of 24 and 10 of 12 on 2026-11-01.

    • Six of those 11 merge groups failed on a verdict-reader defect that #12717 has since fixed. Without them, holds are 5 of 12.
    • On PRs, the count is 7 of 25 if "review lanes not yet complete" is counted as a hold, and 4 of 21 if the four outage-signature timeouts are left out of the denominator.

    This extends the close-out comment, which read every failed run from 01:46:51Z to 04:58:48Z. This one is a seeded sample over the whole window. It runs past #12717 (06:53Z), and it separates the review-lane outage.

    What ran

    • Method: as in the 2026-11-01 baseline. A seeded random sample of failed pr-require-checks runs, with each cause read from the failing job, its step and its failure annotation text.
    • Windows:
      • A, 01:46:51Z to 08:51Z, is the main comparison.
      • B, 08:51Z to 13:33Z, is reported apart because the AI review lanes are down repo-wide.
    • Seed: 12446. The draw is random.Random(12446).sample over each window's failed runs, sorted by id.
    • Populations count failed runs whose latest attempt is failure, by created_at:
      • A: 105 pull_request (25 sampled) and 30 merge_group (12 sampled).
      • B: 47 pull_request (10 sampled) and 1 merge_group (1 sampled). The B pull_request sample was drawn from the 46 runs known at draw time. 41638442524 was still in progress in the cache and failed later.
    • Data sources:
      • Populations come from the hub's local runs cache, which holds runs up to 12:48:18Z, plus one live list call for later runs.
      • Of the 48 sampled runs, 35 had their cause in the hub's fail-causes cache, which stores the failing job and its first failure annotation for each run attempt.
      • The other 13 were read live through actions/runs/{id}/attempts/{n}/jobs and check-runs/{id}/annotations.
      • As a check on the cache, 3 cached runs were re-read the same way, and all 3 matched.
    • Calls: 40 GitHub calls, with no rate-limit answer.
    • Readability: every sampled run was readable.
    • Raw per-run classification: .work/coordinator/resample-12446-runs.json (local).

    Why ci-status is red

    In this comment, "hold" means a hold label, a hold status, or the lanes App hold step. Review lanes not yet complete are reported but not counted as a hold. The baseline's rows sum to more than its run count, so which rows made up its 17 holds is UNVERIFIED. Its do-not-merge, pr-unblock and needs-human rows add up to exactly 17.

    Cause Baseline 11-01 PR (of 24) / MG (of 12) A: PR (of 25) / MG (of 12) B: PR (of 10) / MG (of 1) Example run
    do-not-merge label 8 / 1 4 / 0 4 / 0 41571796196
    pr-unblock failure status on the head 6 / 1 0 / 0 0 / 0 step removed by #12522
    needs-human label 3 / 0 0 / 0 0 / 0 none sampled; 1 in B's cached population (41618922175)
    Lanes App body hold or member veto n/a / 6 0 / 0 0 / 0 none sampled
    Lanes App hold step: "the verdict reader read no artifact listing" (new; #12558 regression) n/a 0 / 6 0 / 0 41574715655
    Queue-time hold: pr-verify-failed label or a pr-verify reader refusal (baseline: hold label added after enqueue) n/a / 4 0 / 5 0 / 1 41576370690, 41608067337
    Holds, total 17 / 10 4 / 11 (5 without the reader defect) 4 / 1
    Review lanes not yet complete on the head 2 / n/a 3 / 0 0 / 0 41582102905
    Review lane ended failure, failure-class=partial (new) n/a 2 / 0 0 / 0 41600546207
    Review lane ended failure, failure-class=timeout (new; review-lane outage) n/a 4 / 0 6 / 0 41601819386, 41616071304
    Contract-only run "superseded by full run" (new; not a hold or a code failure) n/a 2 / 0 0 / 0 41578100432
    check-plugins: "The queued pull request could not be resolved" (new; a resolve error, not code) n/a 0 / 1 0 / 0 41591364459
    Real lane failure (lint-repo, test-node, test-bash, test-python, a failed ci-lanes verdict) 7 / 2 10 / 0 0 / 0 41564658657

    Notes:

    • pr-verify-failed is a separate label from do-not-merge. It caused 5 of the 12 sampled window-A merge-group failures.
    • Outage timing:
      • The timeout class starts before 08:51Z. The four window-A timeout runs were created between 08:14Z and 08:34Z.
      • In the fail-causes cache, the first review leg with failure-class=timeout was created at 08:19:01Z (41602368018). Every review failure from then until 09:30Z is that class.
      • The annotations read here say "the review leg reached its time limit" and do not show fallback=credit-stop. Linking these timeouts to the credit stop is UNVERIFIED. Either way, none of them is a hold.
    • Verdict-reader failures: all six are merge groups created between 03:52Z and 04:22Z. Each failed in the step then named "Fail a lanes App pull request its body or a member holds". All fall after #12558 (03:19Z) and before #12717 (06:53Z). None was sampled after 06:53Z.
    • Kill switch: 41579989876 is counted as a real failure, because test-node failed at "Test resolve-config". Its ci-status also reports that the security review did not run because a kill switch was on.

    What changed on main since the baseline

    These are the hold-step changes in pr-require-checks.yml from git log --since=2026-11-01 -- .github/workflows/pr-require-checks.yml:

    • #12522 (f65ddab412, 01:25Z, before window A):
      • Removes the human-merge-hold and pr-unblock status steps.
      • The lanes App body hold now applies do-not-merge. A member's veto is still read from the comment.
      • Stuck review-finding routes move from needs-human to needs-session.
      • This is why the pr-unblock, needs-human and body-hold rows are 0.
    • #12558 (886fd0f5c6, 03:19Z): turns on FAIL index mode, which caused the verdict-reader merge-group failures.
    • #12725 (db1cec1cf6, 05:47Z):
      • Renames the merge-holds step and changes its verdict line.
      • Drops the ci-status re-run after pr-unblock.
      • Its body names a fail-closed window, "merge-holds.mjs exited 0 without its verdict". Three window-A PR runs in the cache show it; none was sampled.
    • #12717 (6dde3ebd0b, 06:53Z): reads a lanes App head's pass by exact name in index mode, which fixes the feat(source-control): commit the legacy FAILs file to turn on FAIL index mode #12558 regression.
    • Earlier verdict-reader changes in the same step:

    Done-when item 5 (red ci-status runs caused by holds are re-sampled and reported here) is met. The close-out comment first met it for 01:46Z to 04:58Z, and this comment confirms it over the full window A:

    • Holds behind red PR runs fell from 71% to 16% (4 of 25), and every PR-side hold sampled is do-not-merge.
    • Merge-group holds are now the pr-verify-failed queue hold and a reader defect that is already fixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority: highSignificant impact, or blocks an imminent release; staff this cycle.status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.work-class: structuralRefactors, migrations, contract changes; cross-cutting and hard to reverse.

Type

Fields

Work status

ready

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions