Files
ersatztv/docs/decisions/records/ci/cancelled-is-not-a-verdict.md
T
3a53c50c45
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 24s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m38s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m13s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m1s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m13s
docs: a cancelled job reads as failure where a monitor actually looks (#817)
`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>
2026-08-22 13:35:21 +00:00

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.