Skip to content

feat(pr-pipeline): hold lanes App human-merge pull requests with a label only a listed human can remove #9437

Description

@kyle-sexton

Refs: #9399

Problem

A lanes App pull request's "a person merges this" decision lives in text the lanes App can edit. The App has pull-requests: write, so a lane run could delete the Hold: line report-outcome writes for a human-merge pull request, or rewrite its Refs: #N first line to Closes #N. enable-auto-merge.mjs would then arm it on the next run, and ci-status's lanes App hold step (merge-holds.mjs) would pass, because both read the same live body. The do-not-merge label is the enforced hold today, and a body Hold: line is advisory. Hold paths and a trusted approval of the head still bind the App on denied paths.

Proposal

When report-outcome opens a human-merge pull request, give it a label that only a person on the trusted-actor list can remove. Reuse the needs-human lift step in ci-status (Fail a review-lane hold an untrusted account lifted) so removal by any other account fails ci-status, and have arm time skip any pull request that carries the label. Cover both cases:

  1. The Hold: line is removed from the body.
  2. Refs: #N is rewritten to Closes #N.

Acceptance

  • A human-merge lanes App pull request carries the label from opening.
  • Neither body edit arms it, and ci-status stays red until a listed human removes the label.
  • Tests pin both cases.

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 23, 2026
  2. github-actions commented on Oct 23, 2026

    @github-actions
    Contributor

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

    Size: Medium
    Complexity: Medium
    Uncertainty: Known
    Flags: none
    Blocked by: none

    Understood: when report-outcome opens a human-merge lanes App pull request, it should add a label that only a trusted-actor-listed person can remove. ci-status would fail if anyone else lifts the label, and arm time would skip any pull request carrying it. That way neither editing out the Hold: line nor rewriting Refs: to Closes can get the pull request armed. The acceptance criteria are clear and tests are required for both cases. I found no duplicate among the open issues. This is hardening of an advisory hold, not a leaked secret, so no person is needed at triage.

    Automated triage, applied by the lanes App.

  3. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 23, 2026
  4. removed
    status: needs-decisionAwaiting a human or maintainer judgment call.
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 26, 2026
  5. kyle-sexton commented on Oct 26, 2026

    @kyle-sexton
    MemberAuthor

    Released for re-triage under the owner's 2026-10-26 rule: a person is involved only for "1. A change in original intent/vision 2. Something comes up during implementation ... that alters intent/vision or the primary design 3. If there is a HIGH/CRITICAL security issue". None applies here, so the hold label came off and intake triage runs again now.

    To hold this issue again, add needs-human (or status: needs-decision) and name which of the three applies.

  6. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.
    priority: mediumReal value, no hard deadline; normal backlog flow.
    on Oct 26, 2026
  7. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 26, 2026
  8. 4 remaining items

  9. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    and removed
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    on Oct 26, 2026
  10. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.

    Intended outcome: report-outcome labels every human-merge lanes App pull request with the human-gated label (needs-human) when it opens it, so a Hold: line removal or a Refs-to-Closes rewrite neither arms it nor turns ci-status green until a trusted-actor-listed person removes the label.

    Acceptance criteria

    • report-outcome.mjs adds the human-gated label (humanLabel, needs-human by default) to the pull request it opens or reuses whenever the plan is a human-merge hold, and a report-outcome.test.mjs case pins this
    • A report-outcome.test.mjs case pins that a pull request that is not human-merge gets no such label
    • pr-require-checks.yml step 'Fail a review-lane hold an untrusted account lifted' counts a labeled event of the human-gated label by the lanes App bot as a hold, alongside github-actions[bot] and claude[bot]
    • scripts/ci-hold-lifted-by.test.sh has a case where the lanes App labeled the pull request and a non-listed account removed the label, and ci-status exits 1
    • scripts/ci-hold-lifted-by.test.sh has a case where a person on trusted-actors.json removes the lanes App's label and the step exits 0
    • enable-auto-merge.test.mjs has a case where a body with the Hold: line deleted but the human-gated label still present gets no auto-merge armed (holdReason returns 'it carries needs-human')
    • enable-auto-merge.test.mjs has a case where the first line is rewritten from 'Refs: #N' to 'Closes #N' with the label still present and nothing is armed
    • The existing pr-pipeline test suites (report-outcome, enable-auto-merge, ci-hold-lifted-by, resolve-config live-config) pass

    Dependencies

    • #​9399 (merged as the native auto-merge arm step this builds on; closed, nothing to wait on)

    Related work

    Sources read

    • .github/scripts/pr-merge/enable-auto-merge.mjs:312-326: holdReason already refuses to arm a pull request carrying do-not-merge or the human-gated label, and separately refuses on the LANE_HOLD body line
    • .github/workflows/pr-require-checks.yml:4118-4190: the lift step counts a hold only from claude-lane-hold comments or labeled events by github-actions[bot] or claude[bot], then fails unless the first later removal is by a human in trusted-actors.json; the lanes App is not among the hold sources
    • .github/scripts/work-resolve-issue/report-outcome.mjs:1376-1381: humanMerge is computed from the issue's labeler; report-outcome.mjs:1476 creates the pull request with no labels
    • .github/scripts/work-resolve-issue/report-outcome.mjs:107: HOLD_LINE is 'Hold: a human merges this', the advisory body line
    • docs/conventions/pr-pipeline.yaml:186-206: some lane slots use labels-absent needs-human, so labeling these pull requests also pauses those lanes; accepted side effect to check in live-config tests

    Automated refinement by the lanes App. Intake triages the issue again with it.

  11. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 26, 2026
  12. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 28, 2026
  13. added
    admission-deferredAdmitted but waiting for WIP room; the hourly work-unblock sweep labels it agent-ready.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    host-runNeeds an unattended run on a host; the host runner takes it, not a person or the issue lane.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    admission-deferredAdmitted but waiting for WIP room; the hourly work-unblock sweep labels it agent-ready.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 28, 2026
  14. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    The work-resolve-issue lane stopped on this issue (patch-protected-path): the change needs a path the issue lane may not push, which a maintainer session builds by hand.
    It added host-run and removed agent-ready: an agent run on a host builds it, and it waits on no decision from a person.

    In the run's words:

    The run stopped early (patch-protected-path): its change needs a path the issue lane may not push, which a person changes instead. In the run's words: patch-protected-path: .github/workflows/pr-require-checks.yml. The acceptance criterion 'ci-status stays red until a listed human removes the label' needs the step 'Fail a review-lane hold an untrusted account lifted' (pr-require-checks.yml:4464) to count a labeled event of the human-gated label by the lanes App bot as a hold. Its jq filter at line 4498-4499 hardcodes the hold actors to github-actions[bot] and claude[bot], so a needs-human label the lanes App adds is not counted and any account's removal of it would turn ci-status green. That workflow path is one this lane cannot push, so the issue cannot be finished as it asks; the report-outcome.mjs labeling and enable-auto-merge tests could land separately, but without the workflow change they would not meet the acceptance.

    What the run says it did not do:

    • report-outcome.mjs labeling of human-merge pull requests, its tests, the enable-auto-merge.test.mjs cases and the scripts/ci-hold-lifted-by.test.sh cases: not started, because the workflow change they depend on is on a protected path
    • check-changelog-fragments and check-affected were not run: the working tree has no change to check

    Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40790007973

    To hand it back to the lane, remove host-run and add agent-ready again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.host-runNeeds an unattended run on a host; the host runner takes it, not a person or the issue lane.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Fields

    No fields configured for Feature.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions