ci: cache cargo output and stop redundant runs - #572
Merged
Conversation
Run 31382758646 spent 12m14s on the Linux build-test job: 2m31s in the debug compile behind `cargo test`, 7m57s in the release compile behind `Build app`, 1m46s on everything else. 86% of that job was Rust compilation, and no workflow in this repository cached any of it, so every run paid it again from scratch. Three changes, in decreasing order of what they are worth: - `Swatinem/rust-cache@v2` in all three workflows, pointed at the `src-tauri` workspace root. Its automatic key covers the job id, the rustc release/host and a hash of the Cargo files, which is not enough for the two matrices: every entry of test_build.yml's shares the job id `build-test`, and build.yml's two windows-latest entries share the rustc host too and differ only in `arch`. Both get an explicit `key`. - `concurrency` with `cancel-in-progress` in test.yml, the shape test_build.yml has had. Grouped by `github.ref`, which differs between the two triggers, and cancelling only pull requests: the push trigger exists so a commit reaching master without a pull request is still tested, and cancelling one master run because another commit landed would put that hole straight back. - `paths-ignore` on test_build.yml's pull_request trigger. Documentation cannot change what the three platforms compile -- no Markdown file is bundled as a resource -- and the suites that read `samples/*.md` as fixtures still run through test.yml, which has no path filter. Safe only while `build-test` is not a required status check, which it is not: master's protection reports no required contexts and `gh pr checks --required` reports none. The reasoning is in the workflow so that making it required cannot quietly strand a pull request. How much of the compile is actually skipped depends on the cache hit rate, which is not knowable before the caches exist. The next pull request measures it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 10, 2026
PathGao
added a commit
that referenced
this pull request
Aug 10, 2026
…ding it out (#574) * ci: write the cargo cache on master only, and stop writing it four more times #572 added the cargo cache and named two things it could not settle. Both turned out to matter, and the measurement is now available: the repository holds 13.22 GB of Actions cache against a 10 GB limit -- 8.76 GB in ~113 npm caches spread over 50 refs, and 4.45 GB in the four cargo caches a single run of #572 wrote. So the limit binds today, and over it GitHub evicts least-recently-used. Cargo caches are the largest entries in the repository, which makes them the ones that vanish: without a bound, two pull requests in flight overflow the budget and the cache becomes a slower way of not caching. `save-if` bounds it. Pull requests restore and save nothing, so the total is one set rather than one set per branch in flight. That only works if something writes on master, and #572 recorded that nothing did: test_build.yml ran on `pull_request` alone, so every cache it wrote was scoped to a pull request branch and no other branch could read it. Every new pull request paid a full cold compile regardless. It now also runs on push to master, carrying the same paths-ignore, and cancel-in-progress is restricted to pull requests there for the reason test.yml already gives -- cancelling a master run because a second merge landed would discard the compile that was going to fill the cache. build.yml loses its cache entirely. Four more entries is another ~4.5 GB of a budget that is already over, spent on the least valuable minutes here: it is workflow_dispatch-only, runs about weekly, and nobody waits on a release build the way they wait on a pull request check. First place to add one back if the limit stops binding. 965 tests pass; svelte-check clean; all three workflows re-parsed as YAML. The one thing not knowable before merging: the first master run is a cold compile that writes the caches every later pull request reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: drop the npm cache, which was spending the budget the cargo cache needs The 10 GB per-repository cache limit is being exceeded, and `save-if` in the previous commit bounds the cargo half of it. This is the other half, and it was the larger one: 113 npm cache entries, 8.76 GB, across 50 refs, most of them pull requests closed weeks earlier. Nothing can bound them. GitHub scopes caches by ref, so setup-node writes one per branch per platform, and there is no `save-if` equivalent -- deleting them by hand, as was done alongside this branch, buys a few weeks before they grow back. What they bought, measured on runs that hit them: `npm ci` in 6s on Linux, 9s on macOS, 15-17s on Windows. `cache: npm` caches `~/.npm`, the download directory, not `node_modules`; `npm ci` still runs and still links all 190 packages, so the saving is the registry fetch alone and the runners sit close to the registry. Seconds per job, in exchange for the budget that makes an eight-minute saving possible. Two assertions in scripts/ciCacheBudget.test.ts hold both halves: every rust-cache use restricts its save to master, and no workflow re-enables the npm cache. Both were mutation-checked -- removing `save-if` fails the first and not the second, re-adding `cache: npm` fails the second and not the first. Neither is a law of nature and the comments say so; the point is that the next change to either has to re-do the measurement rather than discover the ceiling again. 965 tests pass; svelte-check clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the measurement said
The Linux
build-testjob of run 31382758646 took 12m14s:run cargo test(debug compile)Build app(release compile)86% of the job is Rust compilation. No workflow in this repository cached any cargo output, so every run recompiled the whole dependency graph from scratch, on every runner.
What this changes
1.
Swatinem/rust-cache@v2in all three workflows, after the toolchain install, pointed atworkspaces: src-tauri— that is the cargo workspace root, and without naming it the action looks forCargo.lockat the repository root and caches nothing.The action's automatic key covers the job id, the rustc release/host and a hash of the Cargo files. That is not enough for either matrix, so both get an explicit
key:build-test, leaving the rustc host as the only discriminator, and the target list the matrix varies never enters the key. Keyed onmatrix.os-name.windows-latestentries share the job id and the rustc host, differing only inarch. Without a key the ARM64 job would restore the x64 job's target directory. Keyed onmatrix.os-matrix.arch.2.
concurrencywithcancel-in-progressin test.yml, the shape test_build.yml already has. Grouped ongithub.ref, which isrefs/pull/<n>/mergeunder the pull_request trigger andrefs/heads/masterunder the push trigger, so the two can never cancel each other.One deliberate deviation from a plain
cancel-in-progress: true: it is${{ github.event_name == 'pull_request' }}. The push trigger exists precisely so that a commit reaching master without ever having been a pull request is still tested — the comment above it says so. Cancelling one master run because a second commit landed would leave the first commit untested, putting that hole straight back. Pull request branches, which are where the redundant runs actually pile up, cancel as normal.3.
paths-ignoreon test_build.yml's pull_request trigger —**.md,pics/**,.github/ISSUE_TEMPLATE/**.I did make this change, because I could confirm it is safe. Two independent checks, both at time of writing:
repos/sftwrdotdev/Markpad/branches/masterreportsprotected: truewithrequired_status_checks.contexts: []andchecks: [].gh pr checks --requiredreports "no required checks" on both open pull requests (fix(find): re-focus the preview find bar on a repeated Cmd/Ctrl+F #560, fix(release): make the recovery path usable, and stop promising updates that do not exist #566).So
build-testis not a required status check, and a pull request whose paths are all ignored is not left waiting on a job that never starts. Ifbuild-testis ever made required, this filter has to go in the same change — the reasoning is written into the workflow so that cannot happen quietly.Why it is safe on the merits: no Markdown file is bundled as a resource (
tauri.conf.jsonhas noresourceskey), so documentation cannot change what the three platforms compile. The frontend suites that readsamples/*.mdas fixtures still run on every pull request through test.yml, which has no path filter — that job also has to keep running becausereleaseWorkflow.test.tsasserts onREADME.md,RELEASING.mdand the workflow files themselves, so a docs-only pull request can genuinely break it.samples/**is deliberately not in the list, though it was on the table. It holds only.mdfiles today, so**.mdalready covers it; naming the directory would additionally drop a future non-Markdown fixture out of the matrix without anyone noticing.What I cannot promise
shared-keywith test.yml, would change that — both have costs and are a call for you, not for this pull request.cargo test, release forBuild app) in test_build.yml, plus four more entries in build.yml. Eviction is least-recently-used, so the rarely-run release caches from build.yml could plausibly push out the pull-request caches that actually matter. If that shows up, dropping rust-cache from build.yml is the first thing to try — it is aworkflow_dispatch-only workflow and does nothing for pull request latency.Not done
build.ymlstill has nocache: npmon itssetup-nodesteps, unlike the two test workflows. Left alone: it is release-only, so it does not affect pull request time, and it is outside what was measured.Testing
npm test— 960 pass, 0 fail. (releaseWorkflow.test.tsreads all three of these files as text; nothing it asserts on was touched.)npm run check— 674 files, 0 errors, 0 warnings.paths-ignoreandconcurrencysit where intended.🤖 Generated with Claude Code