Commit 8ebc8fb
ci(signal): name the JOB that ejected a pull request, not the workflow
The attribution step has been titled "Say which job ejected it" since BACKLOG #1403
and interpolated `github.event.workflow_run.name` -- the WORKFLOW name. So every
merge-queue ejection comment read "CI failed ...", a fact the reader already had from
the label and the checks tab, and named no job at all. #1403's banner reads closed
over that gap; this is its unmet acceptance.
git show origin/main:.github/workflows/failure-signal.yml
# step "Say which job ejected it ..." -> RUN_NAME: ${{ github.event.workflow_run.name }}
The step now reads the failing run's jobs and names the leg and its first failing
step: "CI failed in web console tests (windows-2025, py3.14) / Step margin -- web
console suite while this pull request was in the merge queue ...". It refuses to name
`CI gate`, which fails in every red run and whose own failing step points at a leg it
does not name -- the same refusal `scripts/ci/report_ci_red.py` makes, and a test now
compares the two rather than trusting them to agree.
THE TOKEN QUESTION, RESOLVED EMPIRICALLY rather than assumed either way. Throwaway run
35171248441 carried this job's exact permissions block minus the new line. The runner
printed `Issues: write / Metadata: read / PullRequests: write` -- no Actions entry, so
the scope really was `none` -- and `GET /actions/runs/<id>/jobs` still returned 200
with the job names. Unauthenticated curl returned 200 too, and a control call to
`/actions/permissions` was refused in the same job, so those successes are not a probe
that cannot see a 403.
`actions: read` is declared anyway, ALONGSIDE the two existing writes. That 200 carried
`X-Accepted-Github-Permissions: actions=read`: the endpoint's stated requirement IS the
scope, and it is served without it only because this repository is public. Resting the
attribution on that accident would make the job name vanish the day the repo turns
private or GitHub enforces its own declaration -- silently, with the comment still
posting, which is this defect wearing the same clothes and found months later. It is a
READ scope and cannot modify code.
The rule is a Python program in the step's `env:` rather than inline jq because it has
to be TESTED. This workflow may not check the repository out -- the zizmor suppression
rests on it running no third-party code -- so it cannot import the identical rule from
`report_ci_red.py`. Held in `env:` it is a string the tests read out of the file and
RUN, so what is exercised is the bytes that ship.
IT FAILS SOFT, AND SAYS WHICH WAY. An ejection comment is the only record there is, so
a refused fetch degrades to the old workflow-only text rather than posting nothing --
and a rule that CRASHED gets its own annotation, distinct from "no job failed", which a
bare `|| true` would have made indistinguishable. All four paths were run end to end
against the shipped shell with a stubbed `gh`.
`test_it_is_least_privilege_and_cannot_modify_code` is split in two. It now asserts the
WRITE SET alone; the exact permissions block is a separate test. The two used to be one
assertion, which meant granting any scope re-blessed the security claim in the same
keystroke. Proven: revoking `actions: read` reds only the exact-block test and leaves
the write-set test green, while adding `contents: write` reds the write-set test on its
own.
MUTATION-PROVEN, eight ways: drop the roll-up refusal, drop the step name, count a
cancelled sibling, stop fetching the jobs, revoke the scope, add `contents: write`,
unguard the fetch, drift the roll-up name from the reader's. Each reddens exactly the
test that claims it and the baseline is green after restore.
KNOWN RESIDUAL, recorded in the file's header rather than fixed: this workflow watches
CI, Security and backlog-hygiene, and does NOT watch the three advisory crons that rule
on CI's own health -- `required-workflow-state.yml`, the new `ci-red-report.yml`, and
`stalled-prs.yml`. A red one reaches nobody, which `.github/required-contexts.txt`
already records happening for about a day. It is written as prose, not as a
`not-watched:` line, for the same reason CodeQL's is: that register is scoped to
REQUIRED workflows and an exclusion naming a non-required one reds
`test_a_recorded_exclusion_names_a_real_workflow_that_really_is_required`. Adding them
to the watch list is not the fix either -- the label says a REQUIRED check went red,
which is false of an advisory cron.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>1 parent 23372f4 commit 8ebc8fb
2 files changed
Lines changed: 299 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
65 | 83 | | |
66 | 84 | | |
67 | 85 | | |
| |||
100 | 118 | | |
101 | 119 | | |
102 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
103 | 141 | | |
104 | 142 | | |
105 | 143 | | |
| |||
148 | 186 | | |
149 | 187 | | |
150 | 188 | | |
151 | | - | |
152 | | - | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
153 | 206 | | |
154 | 207 | | |
155 | 208 | | |
| |||
168 | 221 | | |
169 | 222 | | |
170 | 223 | | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
171 | 259 | | |
172 | 260 | | |
173 | | - | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
174 | 289 | | |
175 | 290 | | |
176 | 291 | | |
| |||
0 commit comments