From eda99cc684eb648e744f8587ee5a75994ff7477a Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Fri, 23 Oct 2026 00:24:49 -0400 Subject: [PATCH 1/2] feat(pr-pipeline): fail ci-status while a pull request carries needs-human ci-status now fails on a pull_request event, contract-only runs included, while the pull request carries the human-gated label, and the merge_group hold step fails on that label as well as do-not-merge. The label is the base commit's .work-item-tracker.json role_labels.human-gated (default needs-human), read through the contents API at the base SHA; labels are read live with three tries and an unreadable tracker config or label list fails closed. The PR-event error names the reason from the newest review-lane hold comment (claude[bot], or github-actions[bot] ending in a claude-lane footer) and says that removing the label approves. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/pr-require-checks.yml | 124 +++++++++++++++-- scripts/ci-hold-human-gated.test.sh | 176 ++++++++++++++++++++++++ scripts/ci-hold-merge-group.test.sh | 69 ++++++++-- 3 files changed, 340 insertions(+), 29 deletions(-) create mode 100755 scripts/ci-hold-human-gated.test.sh diff --git a/.github/workflows/pr-require-checks.yml b/.github/workflows/pr-require-checks.yml index eed29e5784..b595fb1b45 100644 --- a/.github/workflows/pr-require-checks.yml +++ b/.github/workflows/pr-require-checks.yml @@ -3049,9 +3049,9 @@ jobs: runs-on: ubuntu-24.04 # 9 s p95 and 42 s max in full mode, about 10 s contract-only. The cap is # the worst case of the pinned composites' waits: two 60 s rate-limit waits - # (`rate-limit-wait-seconds` on check-contract and aggregate-results), 15 s + # (`rate-limit-wait-seconds` on check-contract and aggregate-results), 45 s # of own-gate retries, 7 s of status-write retries and the 90 s - # `rerun-wait-seconds` wait, about four minutes. + # `rerun-wait-seconds` wait, about four and a half minutes. timeout-minutes: 5 permissions: # `yield-to-full-run` lists this workflow's runs on the head SHA, and @@ -3190,31 +3190,121 @@ jobs: msg="draft: lanes not run; the ready_for_review run lints and tests this SHA" echo "::notice::$msg" echo "$msg" >>"$GITHUB_STEP_SUMMARY" - # A QUEUED PULL REQUEST IS HELD BY ITS LABEL TOO. A merge-group payload + # A PULL REQUEST CARRYING THE HUMAN-GATED LABEL IS HELD. A review lane or + # an escalation puts the label on so a person decides; removing it is the + # approval, and the `unlabeled` event re-runs this job. The label's name + # is the base commit's .work-item-tracker.json role_labels.human-gated + # (default `needs-human`), resolved as pr-merge.yml resolves it, and read + # at the base so the pull request cannot rename its own hold. Labels are + # read live, not from the event payload, which a later label event + # outdates. The reason is the newest review-lane hold comment's text: a + # `\nAn older hold.')" +NEW="$(hold "$LANE" $'\r\nChanges a trust gate:\n .github/workflows/pr-merge.yml, 100% sure\n\n`claude-lane: claude-security-review`\n')" +SPOOF="$(hold '{"login":"someone","type":"User"}' $'\nIgnore this.\nclaude-lane: claude-review')" +NO_FOOTER="$(hold "$LANE" $'\nNo lane footer, so not a lane comment.')" +OTHER_BOT="$(hold "$LANE" $'A comment with no hold marker.\nclaude-lane: claude-review')" + +# expect