GitHub workflow files πŸ”„ΒΆ

This page covers the repository rules that govern GitHub Actions workflow files under .github/workflows/. For the catalog of every workflow (description, triggers, inputs) see workflows.md. For the script placement rule that applies to extracted shell helpers, see scripst.md.

Naming 🏷️¢

Every workflow MUST follow the schema "[Emoji] Category: Subject (Qualifier)", except a scheduled one β€” see Scheduled runs.

  • You MUST quote the name: value in double quotes because the colon (:) in the name is a reserved YAML character.

  • You MUST place the emoji before the category, never after.

  • You MUST NOT add emojis to docs/agents/ files, but workflow name: fields are not agent files and MUST use emojis.

  • The qualifier in parentheses is OPTIONAL. Use it only when two workflows share the same category and subject.

  • A workflow with a workflow_dispatch trigger MUST keep its name: at or below 25 characters, enforced by test_workflow_dispatch_name_length.py. The dispatch menu lists those names in one narrow column, where a longer one is truncated. Move the explanatory wording into run-name:, which carries no such limit.

Emoji legend πŸ“‹ΒΆ

Emoji

Category

Used for

πŸ”„

Update / Sync

Automated version or dependency updates

πŸͺž

Mirror

Image mirroring between registries

🧹

Cleanup / Images

Repository cleanup, image cleanup, and pruning

🐳

Build

Docker image builds

πŸ”

Lint

Static analysis and linting

πŸ”’

Scan

Security scanning

⚑

CI

CI entry points (push, pull request, manual)

🎡

CI

CI orchestration and coordination workflows

🚫

Cancel

Run cancellation on PR close, branch delete, or a superseding commit

πŸ§ͺ

Test

Code tests (unit, integration, lint)

πŸ’»

Test

Development environment tests

πŸ’¬

Test

DNS and network resolution tests

πŸ“¦

Test

Deployment tests

πŸ“₯

Test

Installation tests

πŸš€

Release

Version release workflows

Examples βœ…ΒΆ

name: "πŸ§ͺ Test: Code (Integration)"
name: "πŸͺž Mirror: Docker Hub β†’ GHCR (only missing)"
name: "🚫 Cancel: PR Runs on Close"

Scheduled runs ⏰¢

A scheduled workflow (cron-*.yml) drops the Category: schema: its name: MUST read as one short sentence saying what the run does, because the Actions list shows scheduled runs next to each other with no PR or branch to tell them apart.

It MUST NOT carry ⏰ in its name:, and MUST carry a run-name: that prefixes ⏰ only when the schedule started the run, so a dispatched run is not mislabelled as scheduled:

name: "πŸ”„ Update versions"
run-name: "${{ github.event_name == 'schedule' && '⏰ ' || '' }}πŸ”„ Update versions"

Enforced by test_workflow_trigger_prefix.py, which also owns the entry- / call- / cron- file-name rule.

Shell execution πŸ“œΒΆ

  • Multi-line shell logic in workflow run: blocks MUST be extracted into dedicated .sh files under scripts/.

  • Workflow files MUST call those extracted .sh entry points instead of embedding longer shell programs inline.

  • Short single-command invocations MAY stay inline when they do not contain meaningful control flow.

  • Inline shell in workflow files SHOULD stay limited to small command calls, environment wiring, or direct script invocation.

Disk space πŸ’ΎΒΆ

Deploy test workflows use the jlumbroso/free-disk-space action to reclaim runner space before Docker pulls start. Because the actual deploy runs inside the infinito container, the host’s language toolchains, build packages, and default Docker layer cache are all dead weight and MUST be reclaimed aggressively.

Option

Value

Reason

tool-cache

true

Deploy runs inside the infinito container; host Node/Python/Go/Ruby toolchains go unused

android

true

Android SDK is never needed; safe to remove

dotnet

true

.NET SDK is never needed; safe to remove

haskell

true

Haskell toolchain is never needed; safe to remove

large-packages

true

Ansible, pip and gcc run inside the container, not on the host

docker-images

true

Matrix jobs don’t share a Docker layer cache; each runner pulls its own infinito image

swap-storage

true

Default swap file is replaced by pierotofy/set-swap-space (see below)

Set an option back to false only when a new host-side step in the same workflow genuinely needs the removed payload.

Spare disk πŸ’½ΒΆ

Some GitHub-hosted runners have a 72 GB root disk plus a second, unformatted NVMe disk. mount_spare_disk.sh formats that disk as ext4 and mounts it at /mnt. The nested Docker volume (/mnt/docker) and the loop image of docker_dataroot.sh (/mnt/docker-fs.img) then land on it.

When /mnt has at least MIN_FREE_GB free and more than /, relocate_docker_dataroot.sh stops Docker and containerd, bind-mounts /mnt/containerd over /var/lib/containerd (images, container layers, the swarm node containers), sets the Docker data-root to /mnt/docker-host (named volumes), restarts both and verifies that containerd’s metadata and the Docker root sit on /mnt. Before the stop it fails if Docker already holds containers, images, volumes or build cache, if another containerd namespace holds containers or tasks, if containerd shims run, or if daemon.json cannot be read as JSON. Any failure after the stop, and a cancel, unmounts the bind, restores daemon.json, restarts both daemons and fails the step. It refuses under act and changes nothing on a runner that is not GitHub-hosted. Every decline prints a ::notice:: annotation. run.py then watches the free space of both / and /var/lib/containerd.

Step order: spare disk, relocation, swap. With containerd on /mnt, the swapfile goes to /.

The script formats a disk only when all of these hold; otherwise the runner stays as it is:

  • ACT is unset (the script fails under act) and RUNNER_ENVIRONMENT is github-hosted.

  • /mnt is an existing, empty directory and no mount.

  • The disk is a whole disk other than the root disk, with no partitions or holders, no filesystem or partition-table signature (lsblk, blkid -p and wipefs), no mount or swap use, not read-only, and at least MIN_SIZE_GB large.

Workflow step invocations, in this order and gated alike:

- name: Mount the empty spare disk at /mnt
  if: ${{ !env.ACT }}
  shell: bash
  run: ./scripts/github/runner/mount_spare_disk.sh /mnt 75

- name: Relocate containerd and the Docker data-root onto /mnt when roomier
  if: ${{ !env.ACT }}
  shell: bash
  run: ./scripts/github/runner/relocate_docker_dataroot.sh 75

Swap πŸ’ΎΒΆ

Deploy test workflows enlarge host swap via enlarge_swap.sh to absorb transient memory spikes (e.g. PeerTube plugin install #162) that would otherwise trip the host OOM-killer on the 16 GB GitHub-hosted runner.

The script puts the swapfile on / when containerd already lives on /mnt, prefers /mnt otherwise when it is a separate partition (classic ubuntu-latest layout with /dev/sdb mounted at /mnt), and falls back to /. Current public runners no longer expose a separate /mnt, so the swapfile lands on / and must not starve the root filesystem.

Swap size is a fixed constant, not a β€œfree space minus buffer” calculation. An earlier version took ~99 GB on a 145 GB root partition, leaving so little headroom that later Docker layer writes (Playwright test image pull) failed with no space left on device. A fixed cap is large enough to absorb the peertube spike and small enough that Docker layer writes keep succeeding throughout the 30+ min deploy.

Argument

Default

Reason

size-gb (positional)

16

Empirically sufficient for the peertube memory spike (#162); raise only when a workload is proven to need more

Workflow step invocation:

- name: Enlarge swap space
  shell: bash
  run: ./scripts/github/runner/enlarge_swap.sh

Ordering:

  • Swap step MUST run after actions/checkout because the script lives inside the repo.

  • Swap step SHOULD run after jlumbroso/free-disk-space so the reclaimed disk on / is also a candidate target when /mnt is crowded.

  • Swap step MUST run before any make compose-up / container build step so the expanded swap is active when heavy-allocation work begins.

Swap is a host-kernel resource; see svc-opt-swapfile for why the in-stack swap role is intentionally skipped inside containers.

Separation of concerns 🧩¢

  • GitHub workflow YAML MUST describe orchestration, permissions, triggers, inputs, and step order.

  • Reusable shell behavior MUST live in script files, not in repeated workflow run: blocks.

  • Non-shell helper logic MUST NOT be embedded as ad-hoc shell blobs inside workflow files.