Repository navigation
ci(nix): make the macOS Nix cache save a trimmed store with the Crane layers - #1921
Conversation
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing: 3
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | ad91367 | Commit Preview URL Branch Preview URL |
Oct 18 2026, 03:25 PM |
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-x64
commit: |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
release.yaml builds the macOS packages with save-nix-cache: false, so its keys (nix-tagpr-build-native-packages-...) never had an entry to restore: the v20.0.35 release (run 39053076844, build-mac-arm64) found no cache, built 161 derivations including the Rust toolchain, fetched 185 paths, and spent 139s in the build step. Key the store by job and matrix scope only, like the sticky disk key. Release builds run on pushes to main, so they now restore the newest entry CI saved on main, often for the very commit being released, and still never write one. The commit sha keeps workflows that share a job id from blocking each other's saves; only runs for the same commit can collide, and their stores hold the same build. The key is computed once in a step and exposed through its outputs so a later change can reuse it outside cache-nix-action's combined step. Existing nix-CI-* and nix-tagpr-* entries stop matching, so each cached job restores cold once.
No CI run has saved a macOS Nix store since #1882 (6ca2776). The build-mac-arm64 leg of every push to main from 2026-10-14T11:14Z to 2026-10-18 (24 runs) restored the same entry, nix-CI-build-native-packages-build-mac-arm64-05c2a29d...-75d48e98, written by the last saving run (38547180232), and its post-job "Cache Nix store" step finished in ~100 ms without a line of output. In cache-nix-action v7.0.2 the only silent exit from the save is `save` not being "true": #1882 started passing `save: ${{ inputs.save }}`, and the post step of a cache-nix-action nested in build-macos-nix-native-package -> setup-nix-cache evidently reads it as empty. Before #1882 it fell back to the "true" default and saved; the single-level tagpr npm job still saves today. So every macOS build restored a four-day-old 1.4 GB entry (~50 s, 47 s of it gtar extraction), found that the deps hash had moved on, and rebuilt the whole Crane chain: deps ~20 s, foundation ~9 s, adapters ~5 s, ccusage ~25 s. setup-nix-cache gains defer-save: it only restores, and exposes the primary key and whether this run may save. The macOS build saves with cache-nix-action/save as an ordinary step, so nothing depends on how post steps evaluate inputs. Saving needs a push to main as well as save-nix-cache, matching the sticky disks: a pull request would only spend ~40 s per leg writing an entry main cannot read, adding eviction pressure on Blacksmith's cache. Before saving, the build roots what the next run needs in the store's own gcroots, like the Linux build: the Crane cacheRoot (keep-outputs holds its toolchain and vendored sources) and the flake inputs this build fetched, found with `nix flake archive --dry-run` so unused pins are not downloaded. The save then collects every other path (gc-max-store-size-macos: 0), so an entry is one build closure instead of accumulating every commit's layers and binaries.
rust-toolchain.toml named no profile, so rustup and rust-overlay used "default", which adds rust-docs: 728 MB and 65,585 files in rust-docs-1.97.1-aarch64-apple-darwin, roughly half the files of the macOS build closure. Every cold build downloads and unpacks it (the v20.0.35 release built rust-docs-1.97.1-aarch64-apple-darwin.tar.xz), and every cache-nix-action entry tars it, a large share of the work in the ~47 s gtar extraction on a macOS restore. Nothing builds or reads the docs. The minimal profile keeps rustc, cargo, and rust-std, and the existing components list still adds clippy, llvm-tools-preview, and rustfmt; rust-overlay also stops pulling libsecret into the Linux toolchain for non-minimal profiles. The toolchain derivation changes, so every Nix store and the Windows cargo cache rebuild once.
Release starts before CI saves the entry for the release commit, so it restores the previous main commit's store, which still matches because release commits only bump package.json files.
6602f62 to
ad91367
Compare
ccusage performance comparisonPR SHA: Performance comparison skipped. Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided. Base package: |
ccusage performance comparisonPR SHA: Performance comparison skipped. Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided. Base package: |
There was a problem hiding this comment.
ℹ️ Review complete — no blocking issues found. Left unapproved per repo policy (reviews do not approve).
Reviewed changes — reviewed the full diff for PR #1921 (macOS Nix cache save fix plus minimal rustup profile).
- Drop the workflow name from the Nix cache key —
setup-nix-cachenow keys stores asnix-<job>-<scope>-<deps>-<sha>with newest-first restore prefixes, so release macOS legs restore the entry CI saved on main.- Save the macOS store explicitly after rooting its closure — new
defer-saveinput plusprimary-key/saveoutputs; the macOS builder restores only, roots the CranecacheRootand fetched flake inputs under/nix/var/nix/gcroots/ci, then saves withgc-max-store-size-macos: '0'so each entry holds one build closure.- Minimal rustup profile —
rust-toolchain.tomlsetsprofile = "minimal", droppingrust-docswhile keepingclippy,llvm-tools-preview, andrustfmtexplicit.Correctness, regression, and release-risk spot checks all passed: the
defer-savesplit matches the documented nested-compositesavepassthrough failure; bothpackages.<system>.ccusage[. -darwin-x64].passthru.workspaceArtifacts.cacheRootattribute paths exist; rooting runs on PRs (validating paths, and the PR run loggedRooted 8 flake inputs) while saving stays gated on main pushes; key sharing between CI and release is content-addressed and matches the existing sticky-disk trust model; theclippyfailure in the PR run is pre-existing (#1905, also failing on main). No security or release-blocking concerns. One cold restore per cached job on the first main push after merge, as noted in the PR.
muse-spark-1.3-contributor (free via Pullfrog for OSS) | 𝕏
Upstream tip: c76874e (2026-10-20, chore(pricing): update models.dev snapshot) Previous fork base: 0c58b88 (the upstream commit this fork was synced to) Synced 83 upstream commits on top of 0c58b88: 56 automated pricing snapshot refreshes and the following meaningful ones: 1cd29f5 chore(deps): update pnpm to v12.13.0 (ccusage#1907) e4f63ba chore(deps): update node.js to >=24.22.0 (ccusage#1908) 130e502 ci: regenerate bun.nix automatically (ccusage#1909) 259ec33 feat(adapter): report stored cost in JSON model breakdowns (ccusage#1903) c1eb448 chore: release v20.0.35 (ccusage#1901) 2a0875f ci(pricing): update snapshots on push to main and daily instead of hourly (ccusage#1914) b090428 chore(deps): update github/codeql-action action to v4.39.0 (ccusage#1912) 4dcf7f5 fix(nix): pin models-dev-gen remeda to models.dev's version and stop Renovate drift (ccusage#1915) 46edb28 fix(nix): stop the dev shell from copying the whole repository (ccusage#1917) 58ec8f1 fix(nix): let commits succeed in worktrees without a loaded dev shell (ccusage#1920) a570a6f fix(nix): vendor Cargo deps without import-from-derivation (ccusage#1918) f6d28c5 fix(antigravity): map model IDs 1418/1419 to gemini-4-argon (ccusage#1905) ce439e5 fix(antigravity): map model ID 1050 to gemini-3.1-flash-lite (ccusage#1902) e62a66f chore(deps): update pnpm to v12.14.0 (ccusage#1923) 117ae68 ci(nix): make the macOS Nix cache save a trimmed store with the Crane layers (ccusage#1921) 7a063a0 ci: shorten the Windows native build critical path (ccusage#1924) 85ebca3 perf(rust): move pricing snapshots into a leaf crate (ccusage#1922) 8fe3f47 perf(nix): build Crane layers with the binary's feature resolution (ccusage#1925) ac289aa perf(pricing): give the pricing refresh a verify-only TLS 1.3 rustls provider (ccusage#1927) d097f74 chore(deps): update rust crate rustls to 0.23.46 (ccusage#1928) f8aaf89 docs: use the compact pkg.pr.new URL for bunx previews [ci skip] (ccusage#1929) f56bb3d chore: release v20.0.36 (ccusage#1916) aaafb41 chore(deps): update dependency vitepress-plugin-group-icons to ^1.8.1 (ccusage#1931) 0861e7f fix(claude): count requestless resp_ content blocks once (ccusage#1935) 867c9f3 chore: release v20.0.37 (ccusage#1933) fa40bfd chore(deps): update songmu/tagpr action to v1.21.3 (ccusage#1937) de99d15 chore(deps): update rust crate zstd to 0.14.3 (ccusage#1936) Fork wins kept: the SQLite on-disk cache with --live-only and the per-namespace ledger, the pi adapter and its per-path namespaces, the OpenCode V2 loader, the FxHash/Cow hot paths, the table renderer, clear-cache, and the tag-only release workflow. Upstream's own release and tagpr machinery stays out. Hand-resolved conflicts: rust/adapters/claude/src/lib.rs: upstream's resp_ message-id dedupe (ccusage#1935) lands beside the fork's requestless replay and collapse_replays rules. rust/adapters/claude/src/README.md: upstream's wording for the msg_/resp_ exception. rust/Cargo.toml: zstd 0.14.3, keeping the fork's exact `=` pin style. .github/workflows/release.yaml: the fork's tag-only release, without upstream's tagpr job or the songmu/tagpr action bump. apps/ccusage/package.json, docs/package.json, package.json: keep 20.0.34; upstream's 20.0.37 bump is dropped. packages/ccusage-*/package.json (6 files): stay deleted; the fork ships no npm wrapper packages and the release workflow does not read them. rust/adapters/claude/src/daily.rs: stays deleted; the fork's daily path lives in lib.rs and shares the dedupe helper the fix changes. Fixes made while landing this sync, folded in so the merge stands green on its own: fix(ci): save the Nix store from the native build composites. The nested cache-nix-action read an empty primary-key at post-job and every native build failed; upstream's defer-save pattern is the fix. fix(rust): keep the agent-recorded cost for deleted sources. The recorded_cost ledger column carries it, so storedCost survives a deleted file. The ledger schema version stays at 2: the column is not a LedgerEntry field, so the blob layout is unchanged and the previously released binary keeps working. fix(rust): make the file fingerprint helper private, the one hawk finding. It is inherited from the pre-sync master, not introduced here. Keep 20.0.34 numbering; DO NOT push a v20.0.34 tag from this fork (release.yaml would publish fork bytes under upstream identity).

Summary
The macOS legs of
build-native-packageshaven't saved a Nix store since #1882. So every macOS build restores the same stale entry and rebuilds the whole Crane chain. This PR makes main save a GC-trimmed store that holds the Crane layers. It also lets release builds restore that store and dropsrust-docsfrom the toolchain.Evidence
build-mac-arm64runs on pushes to main from 6ca2776 (ci: keep the Linux Nix store on a Blacksmith sticky disk #1882, 2026-10-14T11:14Z) to f6d28c5 restored the same key,nix-CI-build-native-packages-build-mac-arm64-05c2a29d…-75d48e98…. Run 38547180232 wrote that entry, and it was the last run to save. In every later run, the post-jobCache Nix storestep finished in about 100 ms with no output (e.g. job 117682344381 in run 39129740960). In cache-nix-action v7.0.2, the only silent exit fromsaveImplisinputs.savenot being"true". ci: keep the Linux Nix store on a Blacksmith sticky disk #1882 started passingsave: ${{ inputs.save }}throughbuild-macos-nix-native-package -> setup-nix-cache, and the nested post step reads it as empty. Before ci: keep the Linux Nix store on a Blacksmith sticky disk #1882 the input fell back to its"true"default and the save worked. The releasenpmjob, which nests only one level, still saves today (nix-tagpr-npm-…).sudo gtar -x. The deps hash in the key has moved on since then, so Crane rebuilds deps (~20 s), foundation (~9 s), adapters (~5 s), andccusage(~25 s).nix-tagpr-…, andsave-nix-cache: falsemeans nothing ever writes it. In the v20.0.35 release (run 39053076844, build-mac-arm64), the restore found no cache. The build step built 161 derivations, including the toolchain, fetched 185 paths (1.8 GiB unpacked), and took 139 s.Not collecting garbage because none of "gc-max-store-size", "gc-max-store-size-macos" are specified), so old Crane layers and binaries pile up in each entry.rust-docsis in every toolchain.rust-toolchain.tomlsets no profile, so rust-overlay usesdefault, which includesrust-docs-1.97.1-aarch64-apple-darwin: 728 MB and 65,585 files. That's about half the files in the macOS build closure (measured locally on the cacheRoot closure).gh cache list, and restores run at ~700 MB/s. So they don't count toward GitHub's 10 GB quota. That quota is currently over its limit (11.97 GB): ~1.6 GBnix-tagpr-npmentries and Windows ARM64 cargo entries. Those entries are out of scope here.Changes
ci(nix): drop the workflow name from the Nix cache key: the key is nownix-<job>-<scope>-<deps hash>-<sha>, the same scheme as the sticky disk key. Release builds run on pushes to main, so they restore CI's newest main entry, often for the same commit, and they still never save.ci(nix): save the macOS Nix store explicitly after rooting its closure:setup-nix-cachegets adefer-saveinput plusprimary-keyandsaveoutputs. For macOS it only restores (cache-nix-action/restore), andbuild-macos-nix-native-packagethen:cacheRootand the flake inputs this build fetched in/nix/var/nix/gcroots/ci. It finds the inputs withnix flake archive --dry-run, so unused pins aren't downloaded. The Linux build rootscacheRootthe same way.cache-nix-action/saveas an ordinary step, only on pushes to main, withgc-max-store-size-macos: '0'. Everything that isn't rooted is collected, so each entry holds one build closure.build(rust): install the minimal rustup profile plus explicit components: removesrust-docs. clippy, llvm-tools-preview, and rustfmt are still listed explicitly.How this will show up
Caches are saved only on pushes to main, so this PR's own run can't show the warm path:
nix-build-native-packages-build-mac-*-…) andCould not find a cache, so they build cold (about the release's 139 s build step). TheRoot current Crane artifacts and flake inputsstep runs and logsRooted N flake inputs, which checks the attribute paths on a real runner.Save Nix storeis skipped. The build log should not list anyrust-docsderivation.Save Nix storelogsCollecting garbage, the store size before and after, andSaving a new cache with the key "nix-build-native-packages-build-mac-arm64-…-<sha>".ccusage-depsderivation should not show up inthese N derivations will be built. The restore should get faster too, since the entry no longer hasrust-docsor accumulated layers (the post-GC store size is in the save log). Release macOS builds should restore CI's entry for the release commit and build little or nothing.Risks / notes
rust-toolchain.toml), restores cold once.rust-toolchain.tomlprofile change also affects the dev shell and rustup users (no local rust-docs). It can be split into its own PR if preferred.PR run results (run 39132229428)
nix-build-native-packages-build-mac-{arm64,x64}-e04fe334…-c77e9cba…) and loggedCould not find a cache, as expected.Rooted 8 flake inputs.testfailure:ccusage-clippyfails onmanual_range_patternsinadapters/antigravity/src/parser.rs:869. That failure comes from fix(antigravity): map model IDs 1418/1419 to gemini-4-argon #1905 and also fails on main (run 39129832136, job 117682623980). It isn't caused by this PR, and it shows clippy still runs with the minimal profile.