ci: build the flake in a non-gating job (task 0041) #50

Merged
alexion merged 1 commits from task-0041-ci-flake-build-job into main 2026-07-20 09:52:42 -04:00
Owner

Task: .claude/tasks/0041-ci-flake-build-job.md

Summary

Adds a distinct flake job to .gitea/workflows/ci.yml that runs nix flake check --print-build-logs on push and on pull request.

Its value is detecting flake rot — most concretely a build-relevant file left out of package.nix's source allowlist — at the commit that causes it, rather than weeks later at the maintainer's next system rebuild.

The job is non-gating by two mechanisms, both needed: no needs edge, so it neither waits on the test job nor is waited on, and continue-on-error, so a red build does not fail the workflow run and therefore cannot block a merge.
Its cost — a cold rebuild of the whole dependency closure, plus a re-run of the fast tier and the installed-binary tier inside the derivation — is stated in the workflow comment alongside the non-gating intent, so neither reads as an oversight.

The allowlist criterion was verified rather than assumed: deleting ./tsconfig.build.json from package.nix's lib.fileset.unions and re-running nix flake check locally fails the build with error TS5058: The specified path does not exist: 'tsconfig.build.json'. package.nix was restored immediately; the probe is not in the diff.

Deviations

continue-on-error sits on the steps, not the job. The obvious spelling was a job key, and the first draft had it there. Gitea's act fork declares RawContinueOnError on its Step struct only — the Job struct has no such field — so a job-level flag is parsed as an unknown key and silently dropped, and a failing check would have failed the run after all: exactly the merge-blocking outcome the criterion forbids, with nothing in the logs to explain it. Gitea's own syntax-comparison page does not list the gap. Step-level is honoured by both act and GitHub Actions, so the fix costs no portability. Recorded as a Gotcha in CLAUDE.md.

Two incidental fixes. The file header comment said "The job runs inside a node container" — singular, and false once a second job exists that deliberately runs without one; now scoped to "The test job". The build step is named "Check the flake (builds the package)" rather than "Build the flake", so the label and the command agree without needing the adjacent comment read first.

Review

Risk

Overall: LOW

  • Blast radius: Low — adds one self-contained CI job plus a task-file update; no source, package, or flake files change and no existing job is touched.
  • Reversibility: Low — deleting the appended YAML block reverts it entirely; no migrations, deletions, or published interfaces.
  • Test coverage: Low — CI config isn't unit-testable, but the one non-obvious criterion (allowlist detection) was empirically probed, and the job itself is the test.
  • Sensitive domain: Low — no auth, secrets, permissions, or data handling; the job uses no tokens beyond default checkout.
  • Size & complexity: Low — ~37 lines, mostly comments; a linear job with no conditionals or matrix interaction.
  • Runtime criticality: Low — dev-only tooling, explicitly non-gating, so failure cannot block merges or affect the shipped package.

Unaddressed findings

Standards — cachix/install-nix-action@v31 is pinned to a mutable major tag. Left as is: it matches how actions/checkout@v4 is pinned elsewhere in the file, and pinning one action by commit SHA while the rest use tags would be inconsistent without being materially safer for a non-gating job that holds no credentials and no secrets access. Worth revisiting as a file-wide decision rather than a local one.

Spec — nix flake check builds only the runner's own system. aarch64-linux and aarch64-darwin are evaluated but not built; --all-systems would not change that, since building them needs runners of those architectures. Left as is: the job's value is the allowlist guard, which is architecture-independent, so this is a limit rather than a shortfall.

Task: [`.claude/tasks/0041-ci-flake-build-job.md`](.claude/tasks/0041-ci-flake-build-job.md) ## Summary Adds a distinct `flake` job to `.gitea/workflows/ci.yml` that runs `nix flake check --print-build-logs` on push and on pull request. Its value is detecting flake rot — most concretely a build-relevant file left out of `package.nix`'s source allowlist — at the commit that causes it, rather than weeks later at the maintainer's next system rebuild. The job is non-gating by two mechanisms, both needed: no `needs` edge, so it neither waits on the `test` job nor is waited on, and `continue-on-error`, so a red build does not fail the workflow run and therefore cannot block a merge. Its cost — a cold rebuild of the whole dependency closure, plus a re-run of the fast tier and the installed-binary tier inside the derivation — is stated in the workflow comment alongside the non-gating intent, so neither reads as an oversight. The allowlist criterion was verified rather than assumed: deleting `./tsconfig.build.json` from `package.nix`'s `lib.fileset.unions` and re-running `nix flake check` locally fails the build with `error TS5058: The specified path does not exist: 'tsconfig.build.json'`. `package.nix` was restored immediately; the probe is not in the diff. ## Deviations **`continue-on-error` sits on the steps, not the job.** The obvious spelling was a job key, and the first draft had it there. Gitea's `act` fork declares `RawContinueOnError` on its `Step` struct only — the `Job` struct has no such field — so a job-level flag is parsed as an unknown key and silently dropped, and a failing check would have failed the run after all: exactly the merge-blocking outcome the criterion forbids, with nothing in the logs to explain it. Gitea's own syntax-comparison page does not list the gap. Step-level is honoured by both `act` and GitHub Actions, so the fix costs no portability. Recorded as a Gotcha in `CLAUDE.md`. **Two incidental fixes.** The file header comment said "The job runs inside a node container" — singular, and false once a second job exists that deliberately runs without one; now scoped to "The `test` job". The build step is named "Check the flake (builds the package)" rather than "Build the flake", so the label and the command agree without needing the adjacent comment read first. ## Review ### Risk **Overall: LOW** - Blast radius: Low — adds one self-contained CI job plus a task-file update; no source, package, or flake files change and no existing job is touched. - Reversibility: Low — deleting the appended YAML block reverts it entirely; no migrations, deletions, or published interfaces. - Test coverage: Low — CI config isn't unit-testable, but the one non-obvious criterion (allowlist detection) was empirically probed, and the job itself is the test. - Sensitive domain: Low — no auth, secrets, permissions, or data handling; the job uses no tokens beyond default checkout. - Size & complexity: Low — ~37 lines, mostly comments; a linear job with no conditionals or matrix interaction. - Runtime criticality: Low — dev-only tooling, explicitly non-gating, so failure cannot block merges or affect the shipped package. ### Unaddressed findings **Standards — `cachix/install-nix-action@v31` is pinned to a mutable major tag.** Left as is: it matches how `actions/checkout@v4` is pinned elsewhere in the file, and pinning one action by commit SHA while the rest use tags would be inconsistent without being materially safer for a non-gating job that holds no credentials and no `secrets` access. Worth revisiting as a file-wide decision rather than a local one. **Spec — `nix flake check` builds only the runner's own system.** `aarch64-linux` and `aarch64-darwin` are evaluated but not built; `--all-systems` would not change that, since building them needs runners of those architectures. Left as is: the job's value is the allowlist guard, which is architecture-independent, so this is a limit rather than a shortfall.
alexion added 1 commit 2026-07-20 07:36:18 -04:00
ci: build the flake in a non-gating job (task 0041)
All checks were successful
CI / test (22) (pull_request) Successful in 48s
CI / test (true, 24) (pull_request) Successful in 1m5s
CI / flake (pull_request) Successful in 3s
CI / test (22) (push) Successful in 49s
CI / test (true, 24) (push) Successful in 1m8s
CI / flake (push) Successful in 3s
710bbfdeac
A distinct `flake` job runs `nix flake check` on push and pull request,
catching flake rot — most concretely a build-relevant file left out of
package.nix's source allowlist — at the commit that causes it rather than
weeks later at the maintainer's next system rebuild.

The job is non-gating by two mechanisms: no `needs` edge, so it neither
waits on the test job nor is waited on, and `continue-on-error` so a red
build does not fail the run. That flag sits on the steps rather than the
job because Gitea's `act` fork declares it on its Step struct only and
silently ignores the job-level key; recorded as a Gotcha.
alexion merged commit 710bbfdeac into main 2026-07-20 09:52:42 -04:00
alexion deleted branch task-0041-ci-flake-build-job 2026-07-20 09:52:42 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/gitea-axi#50