fix(ci): cache the cargo registry, not the 16 GB target dir
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 20m10s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m17s
Traceability Validation / Check Requirement Traces (push) Successful in 15s
Build & Release / Run Tests (push) Successful in 13m59s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m13s
Build & Release / Build Linux (push) Failing after 16m29s
Build & Release / Build Android (push) Canceled after 0s
Build & Release / Create Release (push) Canceled after 0s
Build & Release / Build Windows (push) Canceled after 11m6s

The runner's 74 GB disk kept filling. Measured on the box: 24 GB of
act cache, 23.18 GB of it created in 20 days -- ~1.15 GB/day against a
30-day-unused / 90-day-used GC, so it could never converge.

Cause: src-tauri/target (16 GB locally: 9.6G debug, 3.2G release, 2.4G
android) was cached under five separate keys, all keyed on
hashFiles('**/Cargo.lock'). The release script stamps the version into
Cargo.lock, so all five invalidated on every chore(release) -- 32
distinct lockfile revisions in three months.

- Cache only registry/index, registry/cache and git/db. registry/src is
  omitted as well: cargo re-extracts it from the 155 MB of .crate
  tarballs rather than storing 1.1 GB extracted.
- Collapse the five per-job keys into one shared cargo-registry key.
  They existed to keep debug/release target artifacts from clobbering
  each other; with target uncached, registry contents are
  target-independent and every job wants the same crates.
- Split cargo-xwin into its own key. It tracks the xwin version in the
  builder image, not our lockfile, so keying it on Cargo.lock was
  re-downloading the whole Windows SDK on every release bump.
- CARGO_INCREMENTAL=0: never reused across runs, 3.5 GB of the debug dir.
- Installer artifact retention 30d -> 7d; tagged releases carry the
  binaries anyway.

Inflow drops from ~5 GB to ~150 MB per lockfile change. Tradeoff: Rust
jobs now compile cold every run (~31min vs ~9min on a cache hit for the
Linux release build). Most runs already paid that, since a release bump
invalidated every key. sccache with a hard size cap is the way back if
it bites.
This commit is contained in:
2026-08-21 11:59:28 +02:00
parent 16658889a2
commit a7365b9511
2 changed files with 102 additions and 43 deletions
+32 -10
View File
@@ -13,6 +13,10 @@ on:
- '**/*.md' - '**/*.md'
workflow_dispatch: workflow_dispatch:
env:
# Incremental state is never reused between CI runs -- pure disk cost.
CARGO_INCREMENTAL: 0
jobs: jobs:
test: test:
name: Run Tests name: Run Tests
@@ -33,13 +37,22 @@ jobs:
- name: Cache Rust dependencies - name: Cache Rust dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
# Registry only -- never src-tauri/target. That directory is ~16 GB and
# was cached under five separate keys, which filled the runner's 74 GB
# disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026).
# registry/src is omitted too: cargo re-extracts it for free from
# registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted).
path: | path: |
~/.cargo/registry ~/.cargo/registry/index
~/.cargo/git ~/.cargo/registry/cache
src-tauri/target ~/.cargo/git/db
key: ${{ runner.os }}-cargo-host-${{ hashFiles('**/Cargo.lock') }} # One shared key across every job. The old per-job keys existed to stop
# debug/release target artifacts clobbering each other; with target no
# longer cached, registry contents are target-independent, so all jobs
# want the same crates. First job to finish saves; the rest restore.
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-host- ${{ runner.os }}-cargo-registry-
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
@@ -129,13 +142,22 @@ jobs:
- name: Cache Rust dependencies - name: Cache Rust dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
# Registry only -- never src-tauri/target. That directory is ~16 GB and
# was cached under five separate keys, which filled the runner's 74 GB
# disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026).
# registry/src is omitted too: cargo re-extracts it for free from
# registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted).
path: | path: |
~/.cargo/registry ~/.cargo/registry/index
~/.cargo/git ~/.cargo/registry/cache
src-tauri/target ~/.cargo/git/db
key: ${{ runner.os }}-cargo-android-${{ hashFiles('**/Cargo.lock') }} # One shared key across every job. The old per-job keys existed to stop
# debug/release target artifacts clobbering each other; with target no
# longer cached, registry contents are target-independent, so all jobs
# want the same crates. First job to finish saves; the rest restore.
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-android- ${{ runner.os }}-cargo-registry-
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
+70 -33
View File
@@ -13,6 +13,8 @@ on:
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
# Incremental state is never reused between CI runs -- pure disk cost.
CARGO_INCREMENTAL: 0
jobs: jobs:
test: test:
@@ -27,13 +29,22 @@ jobs:
- name: Cache Rust dependencies - name: Cache Rust dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
# Registry only -- never src-tauri/target. That directory is ~16 GB and
# was cached under five separate keys, which filled the runner's 74 GB
# disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026).
# registry/src is omitted too: cargo re-extracts it for free from
# registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted).
path: | path: |
~/.cargo/registry ~/.cargo/registry/index
~/.cargo/git ~/.cargo/registry/cache
src-tauri/target ~/.cargo/git/db
key: ${{ runner.os }}-cargo-host-${{ hashFiles('**/Cargo.lock') }} # One shared key across every job. The old per-job keys existed to stop
# debug/release target artifacts clobbering each other; with target no
# longer cached, registry contents are target-independent, so all jobs
# want the same crates. First job to finish saves; the rest restore.
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-host- ${{ runner.os }}-cargo-registry-
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
@@ -88,21 +99,25 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
# ⚠️ Key must NOT collide with the test job's `cargo-host` key: the test
# job runs first and saves debug/clippy artifacts under its key, and
# actions/cache skips saving on an exact-key hit — so a shared key meant
# this job's *release* artifacts were never cached and every Linux release
# build compiled cold (~31min vs ~9min for the correctly-keyed Windows job).
- name: Cache Rust dependencies - name: Cache Rust dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
# Registry only -- never src-tauri/target. That directory is ~16 GB and
# was cached under five separate keys, which filled the runner's 74 GB
# disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026).
# registry/src is omitted too: cargo re-extracts it for free from
# registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted).
path: | path: |
~/.cargo/registry ~/.cargo/registry/index
~/.cargo/git ~/.cargo/registry/cache
src-tauri/target ~/.cargo/git/db
key: ${{ runner.os }}-cargo-linux-release-${{ hashFiles('**/Cargo.lock') }} # One shared key across every job. The old per-job keys existed to stop
# debug/release target artifacts clobbering each other; with target no
# longer cached, registry contents are target-independent, so all jobs
# want the same crates. First job to finish saves; the rest restore.
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-linux-release- ${{ runner.os }}-cargo-registry-
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
@@ -160,7 +175,7 @@ jobs:
with: with:
name: jellytau-linux name: jellytau-linux
path: dist/linux/ path: dist/linux/
retention-days: 30 retention-days: 7
build-windows: build-windows:
name: Build Windows name: Build Windows
@@ -178,14 +193,31 @@ jobs:
- name: Cache Rust dependencies - name: Cache Rust dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
# Registry only -- never src-tauri/target. That directory is ~16 GB and
# was cached under five separate keys, which filled the runner's 74 GB
# disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026).
# registry/src is omitted too: cargo re-extracts it for free from
# registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted).
path: | path: |
~/.cargo/registry ~/.cargo/registry/index
~/.cargo/git ~/.cargo/registry/cache
~/.cache/cargo-xwin ~/.cargo/git/db
src-tauri/target # One shared key across every job. The old per-job keys existed to stop
key: ${{ runner.os }}-cargo-windows-${{ hashFiles('**/Cargo.lock') }} # debug/release target artifacts clobbering each other; with target no
# longer cached, registry contents are target-independent, so all jobs
# want the same crates. First job to finish saves; the rest restore.
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-windows- ${{ runner.os }}-cargo-registry-
- name: Cache Windows CRT/SDK (cargo-xwin)
uses: actions/cache@v3
with:
path: ~/.cache/cargo-xwin
# Contents track the xwin version baked into the builder image, not our
# lockfile -- keying this on Cargo.lock re-downloaded the whole SDK on
# every release bump. Bump the suffix by hand if the image's xwin moves.
key: ${{ runner.os }}-cargo-xwin-v1
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
@@ -216,7 +248,7 @@ jobs:
with: with:
name: jellytau-windows name: jellytau-windows
path: dist/windows/ path: dist/windows/
retention-days: 30 retention-days: 7
build-android: build-android:
name: Build Android name: Build Android
@@ -235,17 +267,22 @@ jobs:
- name: Cache Rust dependencies - name: Cache Rust dependencies
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
# Registry only -- never src-tauri/target. That directory is ~16 GB and
# was cached under five separate keys, which filled the runner's 74 GB
# disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026).
# registry/src is omitted too: cargo re-extracts it for free from
# registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted).
path: | path: |
~/.cargo/registry ~/.cargo/registry/index
~/.cargo/git ~/.cargo/registry/cache
src-tauri/target ~/.cargo/git/db
# `-release` suffix keeps this distinct from build-and-test.yml's # One shared key across every job. The old per-job keys existed to stop
# android-check key, whose `cargo check` artifacts would otherwise # debug/release target artifacts clobbering each other; with target no
# claim the key first and block this job's release cache from ever # longer cached, registry contents are target-independent, so all jobs
# being saved (same collision as the Linux job above). # want the same crates. First job to finish saves; the rest restore.
key: ${{ runner.os }}-cargo-android-release-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-android-release- ${{ runner.os }}-cargo-registry-
- name: Cache Node dependencies - name: Cache Node dependencies
uses: actions/cache@v3 uses: actions/cache@v3
@@ -309,7 +346,7 @@ jobs:
with: with:
name: jellytau-android name: jellytau-android
path: dist/android/ path: dist/android/
retention-days: 30 retention-days: 7
create-release: create-release:
name: Create Release name: Create Release