`ci.cancelled-is-not-a-verdict` documented the run/job API, where cancelled is distinguishable. The endpoint a CI monitor actually polls — `commits/{sha}/status`, the per-sha view the merge gate reads — has no `cancelled` state and reports one as `failure`. The record now says to resolve the job-level `conclusion` before reporting a red.
The kickoff's push HARD CONSTRAINT is tightened from "a review has run" to "a CLEAN verdict, zero outstanding findings on the current tree", since #790's rounds 7 and 8 each still found a real mechanism defect and every earlier push auto-cancelled a live run.
Refs #790
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2.9 KiB
key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
| key | title | status | since | supersedes | superseded-by | rule | signals | mechanics |
|---|---|---|---|---|---|---|---|---|
| ci.cancelled-is-not-a-verdict | 2026-07-21 — `cancelled` is not `failure`; a cancelled run is no verdict (#542) | active | 2026-07-21 | none | none | Treat a `cancelled` conclusion as "no verdict" — never as pass or fail — and report FAILED and CANCELLED counts separately in any CI monitor. THE COMBINED COMMIT-STATUS ENDPOINT CANNOT EXPRESS THIS: `GET /repos/{o}/{r}/commits/{sha}/status` has states `success`/`failure`/`pending`/`error` and NO `cancelled`, so it reports a cancelled job as `failure`. Anything polling that endpoint — which is what a CI monitor naturally polls, because it is the per-sha view the merge gate reads — must resolve the job-level `conclusion` via `actions/runs/{id}/jobs` before reporting a red. | conclusion cancelled · run-level vs job-level conclusion · pre-cancel genuine failure · CI monitor state != pending · phantom failure · commit-status endpoint has no cancelled state · combined status reports cancelled as failure · resolve job conclusion before reporting a red · paths: n/a · issues: #542, #790 | Gitea Actions run/job API; monitor logic, e.g. `fail=[j for j in jobs if j['conclusion']=='failure']; canc=[j for j in jobs if j['conclusion']=='cancelled']`. |
The operator cancels runs by hand — they are the only party who can (see
ci.batch-pushes-no-cancel-route) — and a run-level conclusion: cancelled means the run produced
no verdict about your diff. Two traps follow. First, a run whose overall state is failure may
hold a genuine job failure that happened before the cancel: check job-level conclusion plus
timestamps rather than attributing the red to the cancel. Second, a cancelled run tells you nothing,
so never claim green on one.
A monitor that only asks "is state != pending" will report a cancelled run as a failure and send the next session debugging a phantom. Split the two counts explicitly.
The endpoint most monitors poll cannot express the distinction at all. commits/{sha}/status is
the per-sha view — the one the merge gate reads and the natural thing to watch a PR head with — and
its vocabulary is success/failure/pending/error. A cancelled job arrives there as failure.
So "split the counts" is not implementable against that endpoint: the job-level conclusion has to
be fetched from actions/runs/{id}/jobs, with the run id taken from the status entry's target_url.
Measured on ersatztv#790 (2026-08-22): three jobs reported failure on a head where everything that
ran had passed. They had been auto-cancelled by the author's own next push. The cause is usually
self-inflicted, which is the other half of the cost ci.batch-pushes-no-cancel-route and
process.local-gate-before-push describe — an early push does not merely waste a runner slot, it
manufactures reds that look like they belong to the diff.