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.

Blame · docs/workflows.md

Line attribution on main · 602 line(s)

70b70264
Keenan Nemetz
15d ago
1 # Workflows
2
eb07ff56
Keenan Nemetz
8d ago
3 A workflow describes what CI runs: which pushes, change requests, issues,
4 comments, schedules, or manual runs trigger it, which steps each job executes, and
5 what it publishes. Machines and execution belong to [runners](runner.md); this page
6 is about the repository side.
70b70264
Keenan Nemetz
15d ago
7
1cde8b9e
Keenan Nemetz
8d ago
8 For every field, limit, and default, see the [Workflow reference](reference/workflow-reference.md),
9 including [what a workflow cannot express](reference/workflow-reference.md#what-a-workflow-cannot-express).
70b70264
Keenan Nemetz
15d ago
10
11 ## Add a workflow
12
13 Workflows live in `.vole/workflows/*.yaml`. When you push a new workflow, Vole
14 checks it before accepting the push, so a broken workflow is rejected early.
15
16 ```yaml
17 version: 1
1cde8b9e
Keenan Nemetz
8d ago
18 name: Verify main
19 description: Build, test and lint every push to main.
70b70264
Keenan Nemetz
15d ago
20 on:
21 push:
22 branches: [main]
23 jobs:
24 build:
1cde8b9e
Keenan Nemetz
8d ago
25 name: Build and test
70b70264
Keenan Nemetz
15d ago
26 runs-on: [linux, arm64] # only a runner with both labels takes this job
27 image: golang:1.27-bookworm
28 steps:
29 - name: test
30 run: go test ./...
31 - name: build
32 run: make dist
33 artifacts:
34 - path: dist/*
35 pages:
36 path: site/ # publish this directory as the repository's site
37 ```
38
1cde8b9e
Keenan Nemetz
8d ago
39 `name` and `description` say what the workflow is for; the file path stays its
57dc79af
Keenan Nemetz
6d ago
40 identity. When `name` is absent, a run shows the file's base name (`verify.yaml`
41 becomes `verify`). A job's `name` is shown in place of its ID on the run page.
42 These fields are display metadata: they do not change what runs, and two
43 workflows may share a name.
1cde8b9e
Keenan Nemetz
8d ago
44
70b70264
Keenan Nemetz
15d ago
45 Every job must name the environment it runs in, with exactly one of `image:` or
46 `image-file:`. A workflow that names neither fails the push that introduced it.
47 See [Images](#images) for the two forms.
48
49 Triggers work like this:
50
eb07ff56
Keenan Nemetz
8d ago
51 - `on.push.branches` limits a workflow to certain branches; a leading `!` excludes
52 what an earlier pattern included.
70b70264
Keenan Nemetz
15d ago
53 - `on.push.tags` limits it to tag pushes.
eb07ff56
Keenan Nemetz
8d ago
54 - `on.push.paths` and `on.push.paths-ignore` filter a push by the files it changes.
55 - A workflow with no `on` trigger at all runs on every branch.
70b70264
Keenan Nemetz
15d ago
56 - A workflow that names `tags:` runs only on tags.
eb07ff56
Keenan Nemetz
8d ago
57 - A workflow that declares only event triggers does not also run on every push.
70b70264
Keenan Nemetz
15d ago
58
1cde8b9e
Keenan Nemetz
8d ago
59 A push queues each job in the workflow as its own run. Jobs are independent and
60 unordered in CI: one job cannot wait for another, read its files, or consume its
61 result. The order in the file matters only to a local `vole workflow run`, which
62 executes the selected jobs in that order.
63
70b70264
Keenan Nemetz
15d ago
64 `runs-on` asks for a runner with every label in the list. A job with no `runs-on`
65 can run anywhere. See [Runners § Start a runner](runner.md#start-a-runner) for what the
66 labels mean on the machine.
67
e7aa0785
Keenan Nemetz
6d ago
68 A job can budget its whole run instead of each step separately. `timeout:` on a job
69 is the clock its steps share, so one long step is one number to write:
70
71 ```yaml
72 jobs:
73 test:
74 image: golang:1.27-bookworm
75 timeout: 2h
76 steps:
77 - run: go test ./...
78 ```
79
80 A step with no timeout of its own runs with what is left of the budget, and a budget
81 that runs out stops the job at the next step. Without the field, each step keeps its
82 own cap and the job has no total. See
83 [Workflow reference § timeout](reference/workflow-reference.md#timeout), which
84 also says why the image build is not part of the budget.
85
d7958024
Keenan Nemetz
6d ago
86 A job's network is closed by default: the container gets whatever the runner can
87 offer, and a runner with no network is the normal case. `network: bridge` asks for
88 one, which a job that downloads dependencies needs, and only a runner that can
89 provide it takes the job. `network: none` withholds it, so a job that should not
90 reach out fails to resolve a host instead of quietly succeeding, even on a machine
91 that could have given it a network. Declaring the field makes the job behave the
92 same way everywhere instead of depending on which runner picked it up. See
93 [Workflow reference § network](reference/workflow-reference.md#network).
94
70b70264
Keenan Nemetz
15d ago
95 When a push starts a job, Vole remembers the exact commit, the exact workflow
96 file, and the exact image definition. Editing any of them later does not change a
97 job that is already queued.
98
eb07ff56
Keenan Nemetz
8d ago
99 ## Triggers beyond a push
100
101 A workflow can react to a change request, an issue, a comment, a schedule, or a
102 maintainer asking for a run:
103
104 ```yaml
105 version: 1
106 on:
107 change-request:
108 types: [opened, updated, ready]
109 issue:
110 types: [opened, closed]
111 comment:
112 types: [created]
113 schedule:
114 - cron: "0 3 * * 1-5"
115 workflow-dispatch:
116 inputs:
117 environment:
118 type: choice
119 default: staging
120 options: [staging, production]
121 jobs:
122 report:
123 image: alpine:3.22
124 steps:
125 - run: jq . "$VOLE_EVENT_PATH"
126 ```
127
128 A change-request run checks out the request's base branch tip, not the proposed
129 head. The head and the changed files are in the event payload, so a workflow can
130 report on a change without executing an unapproved contributor commit.
131
132 An issue, comment, or schedule run uses the default branch tip. A manual run uses
133 the branch chosen in the browser or with `--ref`.
134
135 The event is a JSON file. The runner sets `VOLE_EVENT`, `VOLE_EVENT_ACTION`, and
136 `VOLE_EVENT_PATH` (`/run/vole/event.json`) for every non-push trigger. Read it as
137 data; do not interpolate it into a shell command. See
138 [Workflow reference § Event data available to steps](reference/workflow-reference.md#event-data-available-to-steps)
139 for the fields.
140
390e3404
Keenan Nemetz
6d ago
141 A workflow is not a template. There is no `${{ }}` and no substitution inside
142 `run`: use `env` or a declared secret for a configuration value, and read the
143 event file when a step needs something the event carries. The event is text
144 someone else wrote, so it stays data instead of becoming part of a command. See
145 [Workflow reference § Context and expressions](reference/workflow-reference.md#context-and-expressions)
146 for the reasoning and an input example.
147
727df386
Keenan Nemetz
7d ago
148 ## Filtering an event
149
150 A trigger can narrow an event to the cases a job cares about. This is how a coding
151 agent can wait to be asked, instead of running on every comment:
152
153 ```yaml
154 version: 1
155 name: Coding agent
156 on:
157 issue:
158 types: [opened, assigned]
159 assigned-to: [coding-agent]
6e1fca51
Keenan Nemetz
6d ago
160 author:
161 role: maintain
727df386
Keenan Nemetz
7d ago
162 comment:
163 types: [created]
164 mentioned: [coding-agent]
165 replied-to: [coding-agent]
166 author:
167 role: maintain
6e1fca51
Keenan Nemetz
6d ago
168 jobs:
169 agent:
170 image-file: agent
171 network: bridge
172 timeout: 2h
ac541b26
Keenan Nemetz
6d ago
173 acts-as: coding-agent
6e1fca51
Keenan Nemetz
6d ago
174 steps:
175 - run: run-the-session
727df386
Keenan Nemetz
7d ago
176 ```
177
178 `mentioned` matches an `@coding-agent` in the issue, comment, or change body.
179 `replied-to` matches a comment that answers one written by `coding-agent`.
180 `assigned-to` matches an issue that was assigned to it, including one opened with
181 that assignment already set. `author.role: maintain` is the ACL filter: it admits
182 maintainers and admins, not contributors or readers. Use `author.name` instead when the
183 rule is a list of people rather than a capability.
184
185 A comment is not treated as addressed to its own author, so the agent's reply does
186 not start it again. Mentions inside code samples are ignored. See
187 [Workflow reference § Event filters](reference/workflow-reference.md#event-filters)
188 for the exact rules.
189
29a795b9
Keenan Nemetz
6d ago
190 The job acts as an account, so the account it uses should be a bot: it has no
ac541b26
Keenan Nemetz
6d ago
191 browser sign-in and is shown as a bot wherever its name appears. `acts-as:` names it, and
6e1fca51
Keenan Nemetz
6d ago
192 the instance gives the job a credential for that account when it is leased — one
193 run's key, written into the job, gone when the run ends — so a comment or a change it
194 writes is attributable and needs no long-lived key in a repository secret. The
195 account's role still decides what it may do. See
196 [Accounts and access § Bot accounts](accounts-and-access.md#bot-accounts) and
ac541b26
Keenan Nemetz
6d ago
197 [Workflow reference § acts-as](reference/workflow-reference.md#acts-as).
6e1fca51
Keenan Nemetz
6d ago
198
199 `network: bridge` is there because the session calls a model; `timeout` because a
200 session can run longer than the ten-minute step default. A job that runs unattended
201 should have both.
29a795b9
Keenan Nemetz
6d ago
202
727df386
Keenan Nemetz
7d ago
203 A comment body is still untrusted text. If the job can write — comment, push a
f24c4142
Keenan Nemetz
6d ago
204 branch, or open a change — use `author.role: maintain` or `author.name` to limit who
205 can start it. Assignment is not a substitute for that filter: whoever opened an
206 issue may assign it, so `assigned-to` alone lets a contributor whose issue it is
207 start the job. Combine the two when only a maintainer should be able to ask. Read
208 the agent's instructions from the repository, never from the event body.
727df386
Keenan Nemetz
7d ago
209
eb07ff56
Keenan Nemetz
8d ago
210 ## Manual runs
211
57dc79af
Keenan Nemetz
6d ago
212 A workflow with `on.workflow-dispatch` can be started by hand. Maintainers and
213 instance admins can start one from a clone:
eb07ff56
Keenan Nemetz
8d ago
214
215 ```sh
216 vole workflow dispatch --workflow .vole/workflows/deploy.yaml \
217 --ref main --input environment=production
218 ```
219
220 The selected branch must exist, and each input is resolved against the types and
221 defaults the workflow declares before anything is queued.
222
e5478085
Keenan Nemetz
11d ago
223 ## Combined results
224
225 A change request that cannot fast-forward needs a merge commit before it can land.
226 Vole builds that merge and runs the jobs that apply to the base branch against it,
227 so the tree that lands is the tree that was tested. The run is queued automatically
228 when the head is pushed, and again when the base moves for a request that is under
229 review or waiting to merge. It uses the workflow files at the merge, so a workflow
230 change on the base applies to the landing, and it does not deploy the repository's
231 site.
232
233 A combined result derived from a contributor branch is untrusted code. It runs only
234 after a maintainer allowed that exact head, and it uses the contributor's cache
235 namespace rather than the one a protected branch builds into.
236
70b70264
Keenan Nemetz
15d ago
237 ## Images
238
239 A job can name a registry image directly:
240
241 ```yaml
242 jobs:
243 build:
244 image: golang:1.27-bookworm@sha256:...
245 ```
246
247 Or keep a reusable definition under `.vole/images/` and reference it by name:
248
249 ```yaml
250 # .vole/images/go.yaml
251 version: 1
252 from: golang:1.27-bookworm
253 env:
254 CGO_ENABLED: "0"
255 run:
256 - apt-get update && apt-get install -y --no-install-recommends jq
257 ```
258
259 ```yaml
260 jobs:
261 build:
262 image-file: go
263 steps:
264 - run: go test ./...
265 ```
266
267 `image-file: go` resolves `.vole/images/go.yaml`. For the complete definition
268 schema, see the [Image reference](reference/image-reference.md). A definition has:
269
270 | Field | Meaning |
271 |---|---|
272 | `version` | Required; currently `1` |
273 | `from` | Required base image reference |
274 | `env` | Optional environment variables for the image |
275 | `run` | Optional build commands; each becomes a `RUN` |
276
277 Definitions are validated when the workflow is pushed, including definitions
278 that are not referenced, and they are pinned with the run. The runner builds the
279 image locally and tags it by the definition digest, so later jobs with the same
280 definition reuse the build. See [Runners § Images](runner.md#images-and-image-builds)
281 for the execution, caching, and security details.
282
283 An image definition is code: its build runs on the runner through the OCI
284 builder, so approving a contributor tip that changes a definition also approves
285 that build. Prefer rootless Podman and pin the base image by digest.
286
287 The image is the dependency manifest. It should contain the toolchains and system
288 packages the builds need, plus `/bin/sh` and a POSIX `sleep` (Vole runs shell steps
289 and keeps the container alive between them). Pin the base image by digest; a
290 mutable tag changes what a build means.
291
3b39a84c
Keenan Nemetz
14d ago
292 ## Environment
293
294 `env` on a job applies to every step, after the image's own, so the job wins:
295
296 ```yaml
297 jobs:
298 test:
299 image: golang:1.27-bookworm
300 env:
301 GOCACHE: /cache/go-build
302 steps:
303 - run: go test ./...
304 ```
305
306 The runner sets `CI=true`, `VOLE_CI=1`, `HOME=/tmp`, `TMPDIR=/tmp`, and
307 `GIT_TERMINAL_PROMPT=0`; a workflow that tries to replace one of those is refused at
308 push time. Everything else is the workflow's. This is how a job opts in to the
309 cache the runner mounts at `/cache`, and how a job using a registry image gets it
310 without an image definition.
311
c1929d2f
Keenan Nemetz
5d ago
312 ## Services
313
314 A job can start a database, a queue, or another server beside it and reach it by
315 name. The job has to ask for a network too, because the service is reached over it:
316
317 ```yaml
318 jobs:
319 test:
320 image: golang:1.27-bookworm
321 network: bridge
322 services:
323 db:
324 image: postgres:17
325 env:
326 POSTGRES_PASSWORD: test
327 PGDATA: /tmp/pgdata
328 steps:
329 - run: |
330 DATABASE_URL=postgres://postgres:test@db:5432/postgres go test ./...
331 ```
332
333 The service's name is its hostname on a private network the runner creates for the
334 attempt. The job and its services are the only members, so `db:5432` is the address
335 a step uses. The service is a container, not a job: it has no steps, no checkout,
336 no secrets, and no files from the runner. At most three may run beside one job.
337
338 The runner starts them after the image is ready and before the first step. A service
339 that exits before the steps start fails the job. There is no health check, so a step
340 that needs a server to accept connections waits for it, as a postgres test waits with
341 `pg_isready`. When the job fails, times out, or is canceled, the end of each
342 service's output is added to the log under `=== service db ===`; a passing run drops
343 it. The job, the services, and the network are removed together, and a killed
344 runner's leftovers are cleaned up by its next start.
345
346 A service image must run as the job's uid, and `/tmp` is writable. The postgres
347 example points `PGDATA` at `/tmp` for that reason. A database image that insists on
348 starting as root does not work here; use one that can run unprivileged or publish a
349 custom image. The runner's memory, CPU, and PID flags apply to each container, so a
350 job with services can use more than one container's share.
351
4ac42680
Keenan Nemetz
7d ago
352 ## Secrets
353
354 A repository can keep the values its jobs need out of its code: a deploy key, a
355 registry token, a staging password. They are stored encrypted on the instance and
356 set by the people who maintain the repository:
357
358 ```sh
359 vole secret set demo DEPLOY_TOKEN # the value is read from stdin
360 vole secret list demo
361 ```
362
363 A job asks for one by name, and the name is also the variable a step reads:
364
365 ```yaml
366 jobs:
367 deploy:
368 image: alpine:3.22
369 secrets: [DEPLOY_TOKEN]
370 steps:
371 - run: ./deploy.sh # reads $DEPLOY_TOKEN
372 ```
373
374 Declaring the name is what makes it present. A job that does not list a secret
375 does not receive it, so a repository's values are never ambient in its steps.
376
377 A name the repository has no value for does not fail the job: the variable is left
378 **unset**, not empty, and the log says which one it went without.
379
380 ```
381 vole: DEPLOY_TOKEN is not set for this repository, so this job runs without it.
382 ```
383
384 That is what makes an optional secret optional. When one is required, the step says
385 so, and the reader gets the message its author wrote:
386
387 ```yaml
388 steps:
389 - run: |
390 : "${DEPLOY_TOKEN:?DEPLOY_TOKEN is not set}"
391 ./deploy.sh
392 ```
393
394 A value is never copied into the workflow, into a container argument, or into a run
395 record: the record names the version of each value the job ran with, so a result
396 says which one it used. Values reach a step only as environment variables, and one
397 is at most 128 KiB, which is as much as one environment variable can carry.
398
e82bbe7d
Keenan Nemetz
7d ago
399 A value is also replaced with `***` in the log as it is written, including one a
400 step prints by accident. That is best effort: a re-encoded value is not caught, a
401 value under four bytes is left alone, and an artifact or a site file is published
402 exactly as the step wrote it. See
403 [Workflow reference § secrets](reference/workflow-reference.md#secrets).
404
4ac42680
Keenan Nemetz
7d ago
405 Anybody who can run a runner for a repository can read the values its jobs declare,
406 because that is the machine they arrive on. Secrets belong to the accounts that
e189a23e
Keenan Nemetz
22h ago
407 approve work and allow CI, and the Secrets tab of the settings page that lists
408 them never shows one.
4ac42680
Keenan Nemetz
7d ago
409 See [Accounts and access](accounts-and-access.md#roles).
410
70b70264
Keenan Nemetz
15d ago
411 ## Contributor branches
412
413 A push to `refs/heads/contrib/<name>/...` queues no work by itself. Those
ab326d3a
Keenan Nemetz
14d ago
414 branches contain code from outside the repository's maintainer group, and running
415 a workflow executes it on a runner. CI is held until a maintainer or admin opens the
f6f4b1db
Keenan Nemetz
15d ago
416 change request for the branch and chooses **Allow CI** there.
70b70264
Keenan Nemetz
15d ago
417
418 The approval is keyed by the exact commit, not by the branch. A new commit or a
419 force-push is a new decision, so the contributor cannot push once and inherit
420 approval forever. The approval is stored in the CI queue database, so a
421 reconciliation or a temporary queue outage does not silently run an unapproved
422 tip.
423
424 Allowing CI queues every workflow that applies to the branch. Workflows with no
425 `on.push.branches` filter run on every branch, including names with slashes such
426 as contributor branches.
427
428 ## Check and run a workflow locally
429
430 You can check workflows before pushing:
431
432 ```sh
433 vole workflow check
434 vole workflow list
435 ```
436
437 You can also run the steps without a server or a runner. From the repository root:
438
439 ```sh
440 vole workflow run # every job in every workflow
441 vole workflow run build # every job in build.yaml
91e6a33a
Keenan Nemetz
14d ago
442 vole workflow run build -j test # one job in one workflow
70b70264
Keenan Nemetz
15d ago
443 vole workflow run -j test # the job named test in every workflow
444 ```
445
91e6a33a
Keenan Nemetz
14d ago
446 A workflow can be named with or without its extension (`verify` or `verify.yaml`). The
3b39a84c
Keenan Nemetz
14d ago
447 command validates the workflow first, with the same checker a push uses, then
70b70264
Keenan Nemetz
15d ago
448 stops at the first failure and exits with that step's status, so it can wrap a
3b39a84c
Keenan Nemetz
14d ago
449 script.
450
451 The steps run in the image the job names, not on your machine. That is the point:
452 the answer here is the answer CI gives, so a toolchain difference between "works
453 for me" and a red run does not exist. An `image-file:` definition is built and
454 cached by digest exactly as a runner would build it, and the working tree is bound
455 into the container, so the steps see your uncommitted edits and leave their output
456 where you would expect.
457
458 The run needs a container runtime — Docker, rootless Podman, or nerdctl — and
459 `--container-runtime` selects one (Docker by default). There is no mode that skips
460 the container: a step that runs in a different environment proves nothing about
461 CI. The default network is `bridge`, because a job that cannot fetch a dependency
462 cannot run here either; `--container-network none` matches what a runner does by
463 default. The container runs as your uid and gid; its filesystem is writable but
464 thrown away when the run ends, and `/tmp` is a tmpfs.
465
466 A local run mounts the same job cache as a runner, under the user cache directory
467 (`$XDG_CACHE_HOME/vole/workflows`, usually `~/.cache/vole/workflows`), so a
468 repeated run is as fast here as it is on a runner. As on a runner, the run sets no
469 variables: the image's `env:` (or the step) decides whether `/cache` is used. See
470 [Runners § Caching](runner.md#caching). `--cache-size` caps it (5G by default) and
471 `--no-cache` empties it after every job.
472
eb07ff56
Keenan Nemetz
8d ago
473 A local run still ignores triggers and `runs-on`, publishes no artifacts,
d7958024
Keenan Nemetz
6d ago
474 and deploys no site. It does apply the job's `network:` request, so a `bridge` job
c1929d2f
Keenan Nemetz
5d ago
475 needs `--container-network bridge` here too (the default), and it starts the job's
476 services on the same kind of private network a runner would. It spends the job's
e7aa0785
Keenan Nemetz
6d ago
477 `timeout:` the way a runner does. It sets `CI=true` and `VOLE_CI=1`, gives each step
478 whatever the job's budget and its own timeout leave it, and reads no standard input.
479 It reads the secrets its jobs declare from the instance the checkout's remote names,
480 with your own credential, so a job that deploys behaves the same here as it does on
481 a runner. Where it cannot — no remote, or no value stored — the variable is unset
482 and the note says which one. Jobs run in declaration order and share the working
483 tree, so a later job sees files an earlier one wrote; a pushed run queues each job
484 separately, starts it from a clean checkout, and has no ordering between jobs. A
485 local run is not an event, so it sets no `VOLE_EVENT*` variables and has no event
486 payload.
70b70264
Keenan Nemetz
15d ago
487
488 ## Artifacts
489
490 Use `artifacts:` to list files that a build should publish. Vole expands the
491 patterns in the checkout after the steps pass, then uploads the files to the same
5ece70a5
Keenan Nemetz
10d ago
492 storage used by LFS and CI artifacts.
70b70264
Keenan Nemetz
15d ago
493
494 ```yaml
495 artifacts:
496 - path: dist/*
497 ```
498
499 Artifacts appear on the run page with a download link:
500
501 ```sh
b7396a11
Keenan Nemetz
2h ago
502 curl -OJ http://host/demo/runs/01M5.../artifacts/tool-linux
70b70264
Keenan Nemetz
15d ago
503 sha256sum tool-linux
504 ```
505
506 A pattern that matches nothing fails the job. A build that stopped producing the
507 declared file is exactly what the declaration is meant to catch. If two matches
508 have the same base name, Vole refuses them so nothing is published under an
509 unexpected name.
510
511 A run artifact belongs to its run. It is not a release artifact. Moving one into a
512 release is a separate step (`vole release attach`). Run artifacts are kept for as
513 long as the run exists.
514
515 ## Build a release
516
517 A tag push can build a release. Mark the artifacts `release: true`:
518
519 ```yaml
520 version: 1
521 on:
522 push:
523 tags: ['v*']
524 jobs:
525 release:
526 image: golang:1.27-bookworm
527 steps:
528 - name: build
529 run: make dist
530 artifacts:
531 - path: dist/*
532 release: true
533 ```
534
535 ```sh
536 git tag -a v1.0.0 -m "first release" # needs notes and a tagger
537 git push origin refs/tags/v1.0.0 # the runner builds and publishes
538 ```
539
b312e166
Keenan Nemetz
10d ago
540 The release commit on the `vole` branch names the run that produced the files
541 (`release: attach ... from run 01M5...`), so `git log vole` still shows which build
542 shipped them. The run's full record is in `data/runs.db`, not on the branch. A
543 lightweight tag is refused: it has no release notes and no tagger. A tag run never
544 counts as a branch's required check, even if the tag points at that branch's tip.
70b70264
Keenan Nemetz
15d ago
545
546 See [Releases](releases.md) for the release page and artifact rules.
547
548 ## Publish a website
549
550 `pages:` publishes a directory as the repository's website, using the same rules
551 as `vole pages deploy`: the branch contains exactly those files, so a page removed
552 from the build stops being served.
553
554 ```yaml
555 pages:
556 path: site/
557 ```
558
559 The commit names the build (`site: main from run 01M5... (6beaa53)`), so
560 `git log pages` shows what put that tree there.
561
562 Vole refuses the deploy if the run is no longer the newest commit on its branch.
563 An older build finishing late must not silently roll the site back. The run log
564 explains why:
565
566 ```text
567 this run built f1f415ec but main is at 6beaa530 now; the site was not deployed
568 ```
569
570 Only one job per workflow can publish the site. The branch name is set by the
571 instance (`pages_ref`), not by the workflow.
572
573 ## What happens to a queued workflow
574
0c14f078
Keenan Nemetz
6d ago
575 Pushing again cancels queued runs for the commit it replaced. It never stops a run
deb024d4
Keenan Nemetz
6d ago
576 that is already in progress; a maintainer can stop that one from its page or with
577 `vole run cancel <id>`, and the runner stops at its next heartbeat. The attempt that
1d06b008
Keenan Nemetz
5d ago
578 comes back is kept as canceled.
70b70264
Keenan Nemetz
15d ago
579
1d06b008
Keenan Nemetz
5d ago
580 A run can have more than one attempt. Each time a runner starts the job it records a
581 new attempt, and a runner that disappears hands the job back for another one. After
70c9ad4e
Keenan Nemetz
1h ago
582 five attempts the run fails instead of cycling.
583
584 An attempt that never reaches the job's first step ends as **could not run**, not
585 **failed**: the image could not be built, the container would not start, or the
586 network the job asked for could not be made. The run page shows the runner's own
587 reason, and the check says the commit is not what failed, so a required check waits
588 instead of going red. It is a rerun away from finishing. A step that exits non-zero
589 is still a failure, and still reds the check, because that one is the code's. A finished run can be queued again
1d06b008
Keenan Nemetz
5d ago
590 with **Rerun** on its page or `vole run rerun <id>`; that is another attempt of the
591 same pinned workflow, image, and secrets, not a new run. A queued or running run
592 cannot be rerun. The run page lists each attempt's log, and the log URL takes
593 `?attempt=N`.
594
70c9ad4e
Keenan Nemetz
1h ago
595 A run's status is `queued`, `running`, `success`, `failed`, `canceled`, or `error`,
596 which the page calls **could not run**. Only the last two are not a verdict about the
597 commit in the same way: a canceled run was stopped on purpose, and an `error` run
598 never started.
599
1d06b008
Keenan Nemetz
5d ago
600 Finished runs are stored in `data/runs.db`, not on the repository's `vole` branch.
70b70264
Keenan Nemetz
15d ago
601 For what happens when a runner disappears, how work is requeued, and how finished
602 history is rebuilt, see [Runners § Scheduling and the queue](runner.md#scheduling-and-the-queue).