Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

#257

Record a job that never started as could-not-run, not as a failure

merged · by keenan 35m ago · merged into main at c4ad0d5a by keenan 22m ago

keenan opened this 35m ago

Closes #246. A run that never executed a step recorded exit=1, steps=nil, and the run page said what it says when a test fails — so a runner that could not build an image red-flagged a commit whose tests passed, and the only way back was a rerun by hand.

The rule

The two facts are “the job ran and something failed” and “the job never ran”. The runner knows the difference exactly once and does not have to guess whose fault it was: if no step ran, nothing about the commit was tested. That is the split, and it is mechanical rather than a per-call-site judgement about machine versus workflow, which the runner cannot always make truthfully.

What changed

  • ci/runs: a record can say where it stopped. Failure=setup means the attempt ended before the job’s first step, with the runner’s own one-line reason in FailureMessage, masked the same way the log is. A run whose newest attempt has that origin is StatusError, not StatusFailed.
  • New columns on runs and run_attempts, added the way every other column here is (additive ALTER TABLE, old databases keep working).
  • ci/agent: the finish path takes an attemptOutcome instead of six positional arguments, which is what lets a failure before the first step carry the reason and a failure after the last one keep the steps that ran instead of dropping them.
  • forge/landing: an error run is a wait, not a refusal. Landing stays blocked because nothing is verified, but the gap names the runner’s reason and the rerun command instead of blaming the commit.
  • Web: the run list counts and filters it, the status reads “could not run” with the reason and rerun advice on the run page, and the change list says “CI could not run”.
  • Docs: workflows.md (the status vocabulary and the new paragraph), runner.md (queue behaviour), change-requests.md (the list’s words).

A step that exits non-zero is unchanged: failed, and it reds the check, because that one is the code’s. The record still carries exit=1, so an old runner and an old instance keep talking; the origin is what carries the new meaning.

Decisions from the issue’s checklist

  • A field on the record, not a guess by the server. The server cannot tell from steps=nil alone whether a workflow with no steps passed or a build never started.
  • The gate waits rather than reading “no opinion”. Silently ignoring it would let a commit land unverified; a wait keeps the gate honest and the message tells the reader what to do.
  • No automatic requeue. MaxAttempts is about flaky tests, not broken runners, and a broken runner requeueing the same job is a hot loop. #247 (in review as #256) stops a runner whose runtime cannot create a container from serving at all, which covers the reported cause at the source; this covers what breaks mid-life.
  • The page says which one happened — status, reason, and what to do — without opening the log.

Testing

New: a setup-failure record round-trips as error with its reason through Get, Attempt, and NewestAttempt; inconsistent records are refused (a step with a setup failure, a reason with no origin, a two-line reason); checkGap over every run status, including the “no reason recorded” fallback; the change list’s new status and its ordering behind a real failure; the runs list and run page rendering a could-not-run row; runStatusLabel; and failureLine’s one-line, bounded, valid-UTF-8 cut.

vole workflow run verify passes (format, vet, build, licenses, govulncheck, packages, deployment files, links, race suite).

Not in this change, on purpose

A run whose attempts all vanish before reporting (RequeueExpired spending MaxAttempts) still ends as failed. That run has no record to say what ran, and from the queue’s side a job that repeatedly kills its runner is not distinguishable from a machine problem; #243 is where that gets a signal.

Discussion (0)

No discussion yet.