Repository navigation
ci: run PR workflows without pull_request_target - #2409
Conversation
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ❌ | Not in draft | PR is still in draft — mark as ready for review when done |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | No source files changed — changeset not required |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | No Copilot review threads |
| ✅ | CI passing | All checks passing |
Files Changed (19 files, +2607 −343)
| File | +/− |
|---|---|
.github/REPO_HEALTH_RUNBOOK.md |
+43 −16 |
.github/workflows/squad-heartbeat-pr-closed.yml |
+26 −0 |
.github/workflows/squad-heartbeat.yml |
+15 −8 |
.github/workflows/squad-impact-request.yml |
+44 −0 |
.github/workflows/squad-impact.yml |
+56 −12 |
.github/workflows/squad-pr-readiness-request.yml |
+47 −0 |
.github/workflows/squad-pr-readiness.yml |
+60 −21 |
.github/workflows/squad-repo-health-report.yml |
+100 −0 |
.github/workflows/squad-repo-health-shadow.yml |
+5 −2 |
.github/workflows/squad-repo-health.yml |
+80 −65 |
scripts/impact-utils/report-generator.mjs |
+1 −1 |
scripts/repo-health-comment.mjs |
+164 −29 |
scripts/repo-health-report.mjs |
+220 −0 |
scripts/workflow-run-pr-binding.mjs |
+272 −0 |
test/assignment-safety-attribution.test.ts |
+10 −1 |
test/pull-request-workflow-split.test.ts |
+380 −0 |
test/scripts/impact-report-generator.test.ts |
+23 −0 |
test/scripts/repo-health-comment-transport.test.ts |
+687 −188 |
test/workflow-run-pr-binding.test.ts |
+374 −0 |
Total: +2607 −343
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
The github org blocks pull_request_target, so heartbeat, impact, pr-readiness and repo-health have failed at startup since the transfer. GitHub also blocks it by default in public repositories from 2026-12-02. Each workflow now does untrusted work under pull_request (read-only token, no secrets) and writes back from a workflow_run job running default-branch code. Writers bind the claim to the run (head SHA, head repository, base repository, open PR targeting dev) before posting. Repo Health decides whether a PR changed its own workflow from blobs pinned to the run's head SHA and the merge base. Closes #2407 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
3473468 to
4c744f2
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Workflow-level concurrency allows skipped fork-PR runs and separate PRs sharing a branch key to cancel readiness refreshes, leaving checklists missing or stale.
1 open issue
What changed in this PR
Restores PR automation after the move to github/squad by replacing blocked pull_request_target triggers with unprivileged PR workflows and default-branch writers, addressing #2407.
Changes:
- Split heartbeat, impact, readiness, and repo-health automation across
pull_requestandworkflow_run. - Add shared PR binding, strict artifact validation, and commit-specific repo-health reporting.
- Update regression tests, shadow verification, the runbook, and the impact-report link.
| File | Description |
|---|---|
test/workflow-run-pr-binding.test.ts |
Tests claim validation and PR/run binding. |
test/scripts/repo-health-comment-transport.test.ts |
Tests artifact transport, reporting authority, and comment lifecycle. |
test/scripts/impact-report-generator.test.ts |
Checks the updated tracking-issue link. |
test/pull-request-workflow-split.test.ts |
Checks workflow triggers, permissions, and split contracts. |
test/assignment-safety-attribution.test.ts |
Updates repository-specific heartbeat trigger expectations. |
scripts/workflow-run-pr-binding.mjs |
Binds claims to workflow runs and live PRs. |
scripts/repo-health-report.mjs |
Validates scanner artifacts and reporting authority. |
scripts/repo-health-comment.mjs |
Validates findings and adds commit-specific refusal notices. |
scripts/impact-utils/report-generator.mjs |
Points the report footer at github/squad. |
.github/workflows/squad-repo-health.yml |
Runs base-commit scanners and uploads results without write permissions. |
.github/workflows/squad-repo-health-shadow.yml |
Extends shadow coverage and updates explanatory comments. |
.github/workflows/squad-repo-health-report.yml |
Adds the default-branch repo-health writer. |
.github/workflows/squad-pr-readiness.yml |
Binds completed runs before refreshing readiness. |
.github/workflows/squad-pr-readiness-request.yml |
Uploads readiness claims from unprivileged PR runs. |
.github/workflows/squad-impact.yml |
Computes and posts impact reports after binding. |
.github/workflows/squad-impact-request.yml |
Uploads impact claims from unprivileged PR runs. |
.github/workflows/squad-heartbeat.yml |
Receives PR-close signals through workflow_run. |
.github/workflows/squad-heartbeat-pr-closed.yml |
Adds the unprivileged PR-close signal workflow. |
.github/REPO_HEALTH_RUNBOOK.md |
Documents trust limitations and split-workflow canary verification. |
Review effort: Max (auto)
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number }} | ||
| group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} |
There was a problem hiding this comment.
Confirmed, it's a regression from this PR. Before it, the group was keyed by PR number (pull_request.number || workflow_run.pull_requests[0].number), so skipped runs couldn't cancel real ones. Tracked in #2420: bind first, then put job-level concurrency on the posting job, keyed by the bound PR number. squad-impact.yml and squad-repo-health-report.yml share the key pattern but aren't affected: each has one trigger and only dev as base.
|
Post-merge authoritative canary for this change: passed (
Runbook gap: the procedure also triggers |
…#2436) #2409 moved the impact, Post Repo Health findings and heartbeat writers onto workflow_run jobs. Their check runs attach to the default-branch tip, not the PR head, and squad-impact.yml / squad-repo-health-report.yml cancel superseded runs, so a quick push on any PR leaves a `cancelled` check on dev. checkCIStatus() treats cancelled/failure as failing CI, so every dev-to-main PR reported "check(s) failing: impact" based on unrelated PRs' runs. readiness-bind was already excluded for this reason (#2423); this adds the remaining workflow_run writer names. Co-authored-by: Copilot <Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

What
Moves the four PR workflows off
pull_request_target:squad-heartbeat,squad-impact,squad-pr-readiness, andsquad-repo-health. Each one now does its untrusted work in apull_requestrun (read-only token, no secrets). Aworkflow_runjob then runs the default branch's code and writes back to the PR with minimal permissions. New files:squad-heartbeat-pr-closed.yml,squad-impact-request.yml,squad-pr-readiness-request.yml,squad-repo-health-report.ymlscripts/workflow-run-pr-binding.mjs,scripts/repo-health-report.mjsThe shadow workflow, the runbook, and their tests are updated too.
Working as release-engineer (Release Engineer).
Why
The
githuborg's Actions policy blockspull_request_target. Since the transfer, every run of these workflows has ended instartup_failurewith "Event 'pull_request_target' is not allowed to trigger Actions workflows" (41 runs, for example 42305876712). GitHub will also block the event by default in public repositories starting 2026-12-02 (changelog).Closes #2407
How
dev;nulllabels, soskip-changelogwas ignored there. It's honored now.pull_requestrun. The PR head is fetched only as Git data.squad-repo-health.ymlis unchanged, judged by comparing blobs at the run's head SHA and its merge base withdev. This closes a time-of-check/time-of-use gap found in review: pushing after approval can't change the result.pull_request, a PR can change the BLOCKING Bootstrap Protection job definition. CODEOWNERS is the compensating control.devdoesn't currently require code-owner review, so maintainers should enable required code-owner review ondev.squad-repo-health.ymlgets no posted findings.workflow_runonly fires for workflows on the default branch, so run the runbook canary and watch the first fork PR.Validation (branch on its own, Windows, Node 27):
npx vitest run test/workflow-run-pr-binding.test.ts test/pull-request-workflow-split.test.ts test/scripts/impact-report-generator.test.ts test/scripts/repo-health-comment-transport.test.ts test/assignment-safety-attribution.test.tsnode --test test/ci-concurrency.test.cjs test/workflows.test.cjsThe other repo-move PRs are #2406, #2403, #2405, and #2404 (issues).
npx changeset add/.changeset/*.md, directCHANGELOG.mdentry for maintainers, orskip-changeloglabel for no user-facing changes): N/A, no SDK/CLI source changesPR Readiness Checklist
Branch & Commit
dev(notmain)dev(git fetch upstream && git rebase upstream/dev)git diff --cached --stat)Build & Test
npm run buildpasses: no package source changes (build verified on the combined repo-move tree)npm testpasses (all tests green): targeted suites above; the full suite runs in CInpm run lintpasses (type check clean)npm run lint:eslintpassesChangeset
npx changeset add(ifpackages/squad-sdk/src/orpackages/squad-cli/src/changed): N/ACHANGELOG.mdentry (maintainers only — write-protected for external contributors)skip-changeloglabel applied (if no user-facing changes)Docs
.github/REPO_HEALTH_RUNBOOK.mdupdatedExports
Breaking Changes
None for users. The contributor-facing behavior changes are listed under How.
Waivers
None.