Why
A maintainer can cancel a run while it is queued, and never after a runner has
picked it up: CancelRun calls CancelQueued, and the comment above it says the
queue does not own the process. So the only ways to stop a running job are its step
timeout, the job budget added in #161, or killing the container on the runner by
hand. For a build that is clearly wrong and expensive — an LLM session on the wrong
track, a retry loop, a test suite that will never finish — none of those is a
minutes-scale answer, and the runner is held for the whole budget.
This is the second half of §3 of #138 (“concurrency groups and cancel-in-progress. A push now only cancels queued runs for the commit it replaced, and never stops a running job”).
Design
- The web action keeps its current shape: a queued run is canceled outright, exactly
as today. A running run gets a
cancel_requestedmarker on the queue row (a new column, additive migration) — the lease is left alone, because the runner holds it and the row must still say who does. - The runner learns about it on its next heartbeat, which already runs every few
seconds while a step executes (
ci/agent.runCommand). The reply gains a cancel field; no new traffic, no new connection, and a runner that is too old to know the field simply ignores it. - On that signal the runner stops the container, says so in the log, and finishes the attempt. The attempt is stored so the partial log and the step timings survive: a canceled run is a run with a story, and “what had it done” is the first question.
runs.Finishmust keep the status canceled rather than mapping the exit code to failed, and the run page should name the account that asked and say the run was stopped rather than that it failed.- Who may cancel does not change: a maintainer or an admin, audited like the queued case today. An account that may not cancel a queued run may not cancel a running one either.
Acceptance criteria
- Canceling a running run stops it at the next heartbeat, records the attempt, keeps
the status
canceled, and writes one line to the log. - The runner does not report the job as failed, and a canceled run never counts as a required check.
- A runner whose lease was lost behaves as it does today: it stops and reports nothing.
- The run page distinguishes “canceled while queued” from “canceled while running”, and names the account.
- The audit log records the account, the run, and the repository.