cad-module was the last red job and now passes. Board updated. Also records that pdf.yml/fuzz reporting "skipped" is correct -- it is gated on schedule || workflow_dispatch -- so nobody spends time investigating it as a failure. The caveat stays prominent: several jobs are green because their gate is deliberately loose (the nigig-map unit-test ratchet sits at 9 real failures, and its fmt/clippy steps are report-only). Those are listed under Known-not-gated so a full green board is not mistaken for a healthy codebase.
7.4 KiB
CI runners
Until 2026-08-02 this repository had zero registered runners and zero workflow runs. Seven workflow files existed and none of them had ever executed — for any commit, on any branch, ever:
GET /api/v1/repos/andodeki/nigig-org/actions/runners -> []
GET /api/v1/repos/andodeki/nigig-org/actions/tasks -> {"total_count": 0}
A gate that never runs is not a gate. Registering the first runner
immediately turned up four defects that had been sitting on main,
described at the bottom of this file.
Registering a runner
gitdab.com is Gitea 1.22 (Actions API compatible with
forgejo-runner). Any Linux host with Docker, or without it in host
mode, will do.
1. Get a registration token
Repo → Settings → Actions → Runners → "Create new runner", or:
curl -H "Authorization: token $GITEA_TOKEN" \
https://gitdab.com/api/v1/repos/andodeki/nigig-org/actions/runners/registration-token
Note this instance answers GET on that endpoint; POST returns 405.
2. Install and register
VERSION=6.3.1
curl -sSLo forgejo-runner \
"https://code.forgejo.org/forgejo/runner/releases/download/v${VERSION}/forgejo-runner-${VERSION}-linux-amd64"
chmod +x forgejo-runner
./forgejo-runner register --no-interactive \
--instance https://gitdab.com \
--token "<registration-token>" \
--name "<runner-name>" \
--labels 'ubuntu-latest:docker://node:20-bookworm'
Every job in .forgejo/workflows/ uses runs-on: ubuntu-latest, so
that label must exist or nothing is ever scheduled.
Host mode (no Docker) also works and is what the first runs used:
--labels 'ubuntu-latest:host://-self-hosted'
Host mode runs jobs directly on the machine as the runner user. Jobs
sudo apt-get install native dependencies and write to $HOME/.cargo,
so use a disposable machine or a dedicated unprivileged user. Docker
mode is preferable for anything long-lived.
3. Run it
./forgejo-runner generate-config > config.yml
./forgejo-runner daemon --config config.yml
Confirm with:
curl -H "Authorization: token $GITEA_TOKEN" \
https://gitdab.com/api/v1/repos/andodeki/nigig-org/actions/runners
"status": "active" means it is polling.
Action resolution — read this before adding uses:
Forgejo resolves uses: owner/name@ref against data.forgejo.org,
not github.com, and there is no fallback. An action that does not
exist there fails the job in Set up job, which cancels every
subsequent step — so the job reports failure having compiled nothing,
and the log looks like an infrastructure blip rather than a config bug.
Verify before committing:
git ls-remote --heads https://data.forgejo.org/<owner>/<name>
Known state:
| Action | Resolves? |
|---|---|
actions/checkout@v4 |
yes |
actions/setup-java@v4 |
yes |
actions/cache@v3 |
yes |
actions/setup-rust@v1 |
no |
android-actions/setup-android@v3 |
no |
Prefer an inline run: step over a third-party action. sms.yml
installs the Android SDK with cmdline-tools directly for this reason.
Reading job logs
The REST API on this instance returns 404 for
/api/v1/repos/{owner}/{repo}/actions/tasks/{id}/logs. The web UI's
JSON endpoint works:
POST /andodeki/nigig-org/actions/runs/<run_number>/jobs/<job_index>/attempt/1
Content-Type: application/json
{"logCursors":[{"step":0,"cursor":null,"expanded":true}, ...]}
<job_index> is the 0-based position of the job in the workflow file,
and <run_number> is run_number from the tasks API — not the task id.
Repeat with the returned cursor per step to page through output.
What the first real runs found
All were invisible defects on main, each fixed in its own commit:
-
android-actions/setup-android@v3does not exist on data.forgejo.org. Theandroidjob insms.yml— the only job that compiles the ~600 lines of JNI behind#[cfg(target_os = "android")]— died inSet up joband cancelled all seven of its steps. -
Five of six
tools/*.shwere committed mode 100644. Every one is invoked as./tools/<name>.sh; bothpay-domain.ymljobs died withPermission deniedat their first substantive step. Now gated inrepo-hygiene.yml. -
cargo-deny --configwas passed to the binary instead of to thechecksubcommand inpay-domain.yml, so the dependency audit exited 2 without auditing anything, skipping the three boundary gates behind it. -
repo-hygiene.ymlwas never scheduled. It was written ason: push: pull_request:Valid YAML, and the spelling GitHub documents for "all branches" — but this instance does not schedule it. The one workflow with no path filter, whose entire purpose is to run on every commit, had never run once.
on: [push, pull_request]fixes it. Use the list form here. -
actions/setup-rust@v1does not exist either, sonigig-map.ymlfails inSet up job. Not fixed — themapcrate does not compile onmainregardless, so that workflow has more than a CI problem.
Current state
Latest result per job, all on a real runner:
| Workflow | Job | Status |
|---|---|---|
| doc-engine.yml | engine, consumer | pass |
| nigig-build.yml | supply-chain, full-crate-check | pass |
| nigig-build.yml | cad-module, full-crate-check, supply-chain | pass |
| nigig-map.yml | test | pass |
| pay-domain.yml | isolated-payment-tests, payment-ui-tests | pass |
| pdf.yml | engine, makepad-integration | pass |
| pdf.yml | fuzz | skipped (schedule / workflow_dispatch only) |
| repo-hygiene.yml | hygiene | pass |
| sms.yml | gates, robius-sms, android, nigig-sms, supply-chain | pass |
Every job passes. pdf.yml/fuzz is gated on
schedule || workflow_dispatch, so skipped is its correct result on
a push.
That is a starting line, not a finish line — see below for what is green because it is genuinely healthy versus green because the gate is deliberately loose.
Known-not-gated
Things that are broken but deliberately not failing a build, so nobody mistakes silence for health:
nigig-mapunit tests: ratchet at 9 failures, not a hard gate. 530 pass; the 9 are real logic bugs (4mvt_parser, 1overpass_parser, 4spriteclassification) that were invisible until the crate compiled.nigig-maptest targets:tests/ui.rsimportsmakepad_widgets::makepad_test;tests/makepad_visual_tests.rsandbenches/tile_decode_bench.rsimportpub(crate)modules, andcriterionis not a declared dev-dependency. The workflow runs--libonly for that reason.nigig-mapfmt/clippy: report-only. 392 pre-existing fmt diffs insrc/(rustfmt could not parseview.rsbefore, so it silently skipped the whole directory) and 132 clippy warnings.
Runner capacity
One runner with capacity: 1 serialises every job; a push touching
several filtered paths takes a while to drain. Raise capacity, or add
runners, if that becomes annoying.
Writing a ratchet step
The runner executes run: blocks under bash -e. A bare
out="$(cargo test ...)"
aborts the step the moment the command exits non-zero — which is always true while a ratchet baseline is above zero — so the comparison never runs and the step fails at exactly the count it was meant to allow. This bit nigig-map.yml on its first run. Use:
status=0
out="$(cargo test ... 2>&1)" || status=$?
|| status=$? makes it a tested compound command, which -e exempts.