What
A job can budget its whole run:
jobs:
agent:
image-file: agent
timeout: 4h
steps:
- run: run-the-session
The budget is a wall clock the steps share, starting with the first step. A step that
declares its own timeout: is cut short when the budget ends first, and a step that
declares none runs with whatever is left — so one long step needs only the job’s
number. When the budget is spent before a step starts, the job stops there with one
line in the log instead of running that step on its own cap.
The field is optional, so a workflow that does not declare one behaves exactly as before: each step keeps its own cap.
Why
Two gaps in one. A job of several slow steps held a runner for as long as they took,
with no number saying what the job was allowed to spend. And one long step could not
be expressed at all: the only clock was per step and its ceiling was an hour, which
is why a coding-agent session or a soak test could not live in a workflow. This
closes the “job-level timeout” item in #138 §3 and removes it from the reference’s
list of things a workflow cannot express.
Shape
- Format (
ci/workflow.go):Job.Timeout, validated at push (positive, ≤ 4 hours), and a step timeout longer than the job’s budget is refused with the reason.MaxTimeoutrises from one hour to four;DefaultTimeoutstays at ten minutes. - One rule for the limit (
ci.StepLimit,ci.BudgetLeft): the step’s own timeout, clamped to what is left of the budget, or the remaining budget when the step declares none. The runner and the local run both call it, so the two cannot drift. - Queue (
ci/runs): the budget is pinned with the run in an additive column, like the steps and the labels, so editing the workflow after the push cannot change what a queued job is allowed to spend. - Runner (
ci/agent): starts the clock with the first step (an image build has its own cap and is not charged), cuts each step to the limit, and reports a spent budget as a failed step so the record keeps its shape. - Local run (
cmd/vole): spends the budget the same way and fails with the same message. - Docs: the limits table, the job field table, a
timeoutsection with the image-build note, the step table’s default, and the removal of “job-level timeouts” from what a workflow cannot express.
Not in this change
Stopping a job that is already running is #162. It is a queue and runner-protocol change (a cancel marker, a heartbeat reply field, a finished attempt that stays canceled) rather than a format one, and it reviews better on its own.
Raising the ceiling to four hours is a judgement call: it covers a long agent session with room to spare, and a workflow that declares a budget above it is refused rather than allowed to hold a runner for a day.
Checks
go build ./...,go vet ./...,gofmt -l .clean.vole workflow run verifypasses in the pinned image (format, vet, build, licenses, govulncheck, packages, links, race tests).- New tests:
TestJobTimeoutIsParsedAndValidatedandTestStepLimitinci(including a past deadline being spent rather than unlimited),TestTimeoutIsPinnedAtEnqueue, the runner’s revalidation of a bad budget,TestWorkflowRunSharesAJobBudgetandTestWorkflowRunStopsWhenTheJobBudgetIsSpentin the local run.
Closes #161.