nigig-org/.forgejo/RUNNER.md
nigig-ci 422a0e3388
All checks were successful
repo hygiene / hygiene (push) Successful in 3s
docs(ci): all jobs green; note that this is a starting line
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.
2026-08-04 06:57:21 +00:00

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:

  1. android-actions/setup-android@v3 does not exist on data.forgejo.org. The android job in sms.yml — the only job that compiles the ~600 lines of JNI behind #[cfg(target_os = "android")] — died in Set up job and cancelled all seven of its steps.

  2. Five of six tools/*.sh were committed mode 100644. Every one is invoked as ./tools/<name>.sh; both pay-domain.yml jobs died with Permission denied at their first substantive step. Now gated in repo-hygiene.yml.

  3. cargo-deny --config was passed to the binary instead of to the check subcommand in pay-domain.yml, so the dependency audit exited 2 without auditing anything, skipping the three boundary gates behind it.

  4. repo-hygiene.yml was never scheduled. It was written as

    on:
      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.

  5. actions/setup-rust@v1 does not exist either, so nigig-map.yml fails in Set up job. Not fixed — the map crate does not compile on main regardless, 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-map unit tests: ratchet at 9 failures, not a hard gate. 530 pass; the 9 are real logic bugs (4 mvt_parser, 1 overpass_parser, 4 sprite classification) that were invisible until the crate compiled.
  • nigig-map test targets: tests/ui.rs imports makepad_widgets::makepad_test; tests/makepad_visual_tests.rs and benches/tile_decode_bench.rs import pub(crate) modules, and criterion is not a declared dev-dependency. The workflow runs --lib only for that reason.
  • nigig-map fmt/clippy: report-only. 392 pre-existing fmt diffs in src/ (rustfmt could not parse view.rs before, 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.