fix(ci): render the review lanes' fork check name instead of its expression source - #8216
Conversation
…me renders The five same-repo AI review lanes name their job conditionally so a fork PR gets an alias instead of the protected check name. GitHub never evaluates a SKIPPED job's `name:`, and the fork guard was a job-level `if:` -- so on every fork PR the check-run was published under the raw expression source, e.g. `github.event.pull_request.head.repo.full_name == github.repository && 'GPT 5.6 Review' || 'GPT 5.6 Review (same-repo lane, not applicable to forks)'`. The guard now sits on every step instead. The job starts on a fork, evaluates its name, runs no step, and completes as an empty success under the intended alias. Same-repo PRs are unchanged. Because the one job-level guard became a per-step guard, a step added later without it would execute against fork content in a lane holding `pull-requests: write` and `id-token: write`. `test_ai_review_workflows.py` now asserts the guard per step, and asserts it never returns to the job level.
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS Root cause correctly named (skipped jobs never evaluate Suggestions
[DESIGN-REVIEWED] 7ab9727 |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of Both grep sweeps confirm the conditional-name pattern ( First-Principles-Verdict: PASS A platform rule (skipped jobs never evaluate What this change shipsIntent: make fork PRs' five review-lane check rows show readable names instead of raw expression source — a FIX.
Grepped [FIRST-PRINCIPLES-REVIEWED] 7ab9727 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsAll five lanes carry the fork guard on every step; I verified design (6/6), codex (14/14), claude (10/10), and first-principles (9/9) directly, and the ux hunks add it to all 7. The composed conditions ( No findings. [OPUS-REVIEWED] 7ab9727 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Problem / Motivation
Every fork PR's check list carries five rows whose names are raw GitHub Actions
expression source rather than a check name, e.g.
One per same-repo AI review lane (GPT, Opus, Design, UX, First Principles).
Observed on #7518; same-repo
PRs render correctly.
Why it matters
Every external contributor sees five unreadable rows on their PR, and a
maintainer scanning a fork PR's checks cannot tell at a glance which lane each
row belongs to. It also makes the collision fix itself look broken: the intended
alias name (
... (same-repo lane, not applicable to forks)) is present only as afragment inside the expression text.
What changed (motivation → approach → change)
Symptom: the fork PR check-run name is the
name:expression's source text.Root cause: GitHub does not evaluate a skipped job's
name:expression. Thefork guard was a job-level
if:, so on a fork PR the job never started, thename was never evaluated, and the check-run was published under the raw
expression body.
Change: the guard moves off the job and onto every step, in all five lanes. On a
fork PR the job now starts, evaluates its name, skips every step, and completes
as an empty success under the intended alias. The unavoidable cost is that a
runner slot is taken for a few seconds per lane per fork PR event; the
alternative (a static job name) is what created the branch-protection collision
that #7982 fixed, so it is not available.
Semantics are preserved rather than re-derived: each existing step condition is
wrapped, so
if: always()becomesif: <guard> && ( always() ). The expressionstill contains a status function, so GitHub's implicit
success()is still notapplied and the post/gate steps still run after a failed review step. Steps with
no status function keep their implicit
success().Removing the single job-level guard turns one fail-closed boundary into 49
per-step ones, in lanes that hold
pull-requests: writeandid-token: write.That regression is closed by test, not by convention: the guard is now asserted
per step, and asserted not to be at job level (which would silently restore
the rendering bug).
Also in the diff: the job-header rationale comments in all five lanes, the
docs/ci/ci-and-reviews.mdsecurity-posture bullet, and one stalepr-readiness.ymlcomment claiming the same-repo lane posts askippedcheck-run under the protected name on forks — untrue since #7982 renamed it.
No behaviour change for same-repo PRs: the job ran before and runs now, and every
step's effective condition is unchanged (
true && (<original>)).Tests
test/test_ai_review_workflows.py, inTestProtectedCheckNameHasOnePublisherPerPrType:test_every_step_carries_the_fork_guard(new, parametrized over all fivelanes) — asserts every step's
if:contains the fork guard, so a step addedlater without it fails CI instead of executing against fork content.
test_same_repo_lane_keeps_the_protected_name_only_for_same_repo_prs(amended) — the old
assert GUARD in job["if"]is replaced byassert "if" not in job, locking out the placement that stops the name fromrendering.
Both are mutation-verified: dropping the guard from one step fails the first,
and re-adding a job-level
if:fails the second.Manual verification
Confirmed the root cause against live check-run data rather than by reading the
YAML: fork PR #7518 publishes all five lanes under raw-expression names with
status
skipping, while same-repo PRs #8199 / #8194 / #8189 publish the exactprotected names. The change is not observable locally — GitHub's own scheduler
decides whether a job is skipped — so the rendered fork name will be verified on
this PR's own CI if it is pushed from a fork, and otherwise on the next fork PR
after merge.
Local gates green: 302 workflow/profile tests, full backend suite matching the
origin/mainbaseline exactly (89 pre-existing environment failures on bothsides), black / isort / flake8, brand, docs-lint, changelog, feature-map,
harness-parity, focus-cue, testpaths, scrub-lint.
Related Issues
no linked issue: follow-up to #7982, which introduced the conditional name this
PR makes render.
Pattern harvest
Rule candidate: review-prompt
Pattern: a workflow expression that is only evaluated on some paths (a skipped
job's
name:) silently ships its own source text as the value.Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)