Skip to content

feat(ci): keep only what cache.nixos.org lacks in the Actions cache - #13

Merged
clhodapp merged 3 commits into
mainfrom
feat/ci-cache-slim
Sep 8, 2026
Merged

clhodapp merged 3 commits into
mainfrom
feat/ci-cache-slim

Conversation

@clhodapp

@clhodapp clhodapp commented Sep 8, 2026

Copy link
Copy Markdown
Owner

The Actions cache entry this pipeline keeps between runs held the whole rooted Nix store: everything the checks' closures reach, including the paths cache.nixos.org serves (glibc, gcc, the language runtimes), which every run could fetch from there instead. This changes what the entry holds so that those paths stay out of it.

How it works now

  • The entry is a Nix binary cache on disk (/home/runner/ci-cache: narinfo files and zstd-compressed NARs) plus the root tree under roots/. Nix reads it as a substituter, listed in nix.conf as file:///home/runner/ci-cache?trusted=true; the trusted store parameter accepts that store's unsigned paths and no other's.
  • After nix flake check, the run copies the closure of the flake's checks and packages into the binary cache, asks cache.nixos.org which of those paths it serves (a HEAD request per path for its narinfo file, 32 in parallel; a failed request counts as not served, which only keeps more), and records the rest as this build's root (roots/<class>/<id>/paths). The whole closure has to go in first, because a binary cache refuses a path whose references it does not hold; the served paths are deleted again before the save, and compressing them with zstd costs seconds.
  • The priority order and the 10 GB budget are unchanged. Sizes now come from the narinfo FileSize fields, the compressed sizes GitHub counts, instead of uncompressed closure sizes.
  • Before the save, every narinfo that no surviving root lists is deleted, then every NAR that no remaining narinfo names.
  • Restore and save use actions/cache/restore and actions/cache/save (v4.3.0) directly. cache-nix-action and its store garbage collection are gone, and the root step needs no sudo.

Transition

Keys move from nix-Linux-<sha> to cache-Linux-<sha>, so no run restores an entry of the earlier form (a store archive, which the new restore step would extract before Nix is installed). The first run on main after this lands is cold and saves the first new entry; the earlier entries expire unused within GitHub's seven-day window. This pull request changes .github/, so check-pr builds it under the request's own scope, which is the first exercise of the new pipeline.

cachix

The push to the clhodapp cache now covers the closure of this run's outputs; earlier runs pushed theirs when they were built, and cachix push still skips what the cache holds. Before, each run re-pushed the closure of every kept root.

Verified locally

actionlint and the repository formatter are clean. The copy, upstream probe, budget, root dropping and pruning logic was exercised with two synthetic roots and a budget that fits only one; a fresh chroot store then substituted the kept path from the file cache and its references from cache.nixos.org. Without the trusted parameter Nix refuses the unsigned path, as intended. The first push of this branch probed upstream through nix path-info --store, which realises its arguments before answering and failed on locally built outputs in the check-pr run; the second commit replaces it with the narinfo requests.

🤖 Generated with Claude Code

Result on this branch

The check-pr run on the final commit saw 15 paths in the closure, none served by cache.nixos.org (the drivers, menus and check outputs are all built here), and kept all 15; the saved entry is 188 KB, against 102 MB for the whole-store entry main holds today. The third commit fixes an awk join that recorded every path as served when the upstream cache served none of them, which this repository hit.

The cache entry becomes a Nix binary cache on disk (narinfo files and
zstd-compressed NARs) that the checks read as a trusted substituter,
instead of an archive of the whole rooted store. After the checks, the
run copies the closure of its outputs into it, asks cache.nixos.org
which of those paths it serves, records the rest as this build's root,
and prunes the cache to the union of the surviving roots' lists before
the save. Sizes for the 10 GB budget come from the narinfo FileSize
fields, so they are the compressed sizes GitHub counts rather than the
uncompressed closure sizes used before.

Keys move from the nix- prefix to cache- so that no entry of the
earlier form is restored; the earlier entries expire unused. The push
to the clhodapp cache covers this run's outputs; earlier runs pushed
theirs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@clhodapp clhodapp self-assigned this Sep 8, 2026
clhodapp and others added 2 commits September 7, 2026 17:47
…info

`nix path-info --store <cache>` realises its arguments against that store with the configured substituters before answering, and fails on a path no substituter has, which every locally built check output is. The probe is now a HEAD request per path for the cache's narinfo file, which is the question being asked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both awk joins used the NR == FNR idiom, which takes the second file for the first when the first is empty: a closure with no upstream-served path was recorded as fully served and the entry saved with nothing in it. The joins now test FILENAME against ARGV[1].

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@clhodapp
clhodapp merged commit a4f4976 into main Sep 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant