Summary
rust-cache works very well for dependency caching, but I ran into a repeated rebuild case when using cache-workspace-crates: true for a Rust workspace with multiple binary targets.
The short version: the target cache key appears to intentionally ignore workspace source contents. That is normally sensible, but when workspace crate artifacts are cached, an exact cache hit can restore stale workspace artifacts and then skip saving the rebuilt target state. The next run restores the same stale target cache again, so the same workspace crates rebuild repeatedly.
It would be useful to have an option to make the target cache key include a user-provided source fingerprint, while keeping Cargo home/dependency cache behavior unchanged.
Use Case
I have a Rust workspace with multiple binary targets built in a CI matrix. Each matrix job uses a separate CARGO_TARGET_DIR.
The goal is for unchanged matrix jobs to be true Cargo no-ops across repeated CI runs, including local workspace crates.
The workflow shape is roughly:
- uses: actions/checkout@v6
- uses: Swatinem/rust-cache@v2
with:
workspaces: ./app -> ../../target-for-one-binary
cache-targets: true
cache-all-crates: true
cache-workspace-crates: true
shared-key: one-binary-target
- run: cargo build --release --bin one-binary
env:
CARGO_TARGET_DIR: /tmp/target-for-one-binary
Observed Behavior
Some matrix jobs repeatedly rebuilt local workspace crates even though the cache restore was a full match and checkout had preserved source mtimes.
The repeated rebuilds were most visible for workspace crates that have build scripts or generated code, for example:
- A parser crate whose
build.rs generates parser code.
- A generated-data crate whose
build.rs reads a JSON file and writes generated Rust into OUT_DIR.
- Binary crates depending on those local generated-code crates.
I added explicit build-script invalidation hints such as:
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=path/to/input-file");
That was good for correctness, but it did not solve the repeated CI rebuild by itself.
What Appears To Be Happening
From reading the action behavior and CI logs, the issue appears to be:
rust-cache restores an exact cache key.
- Cargo detects some workspace artifacts are stale and rebuilds them.
- Because the restored key was an exact hit,
rust-cache post step reports Cache up-to-date.
- The rebuilt workspace target state is not saved under a new key.
- The next run restores the same stale target state and rebuilds the same workspace crates again.
This makes sense for dependency-only caching, but it is surprising when cache-workspace-crates: true is enabled and the desired behavior is to preserve workspace crate artifacts too.
Workaround That Fixed It
I split the cache responsibilities:
- uses: Swatinem/rust-cache@v2
with:
workspaces: ./app -> ../../target-for-one-binary
cache-targets: false
cache-all-crates: true
shared-key: one-binary-cargo-home
- name: Compute source key
id: source-key
run: |
hash="$({ git rev-parse HEAD:app; git ls-files -s app; } | sha256sum | cut -d ' ' -f1)"
echo "hash=$hash" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v5
with:
path: /tmp/target-for-one-binary
key: target-v1-${{ runner.os }}-${{ runner.arch }}-one-binary-${{ steps.source-key.outputs.hash }}
restore-keys: |
target-v1-${{ runner.os }}-${{ runner.arch }}-one-binary-
- run: cargo build --release --bin one-binary
env:
CARGO_TARGET_DIR: /tmp/target-for-one-binary
The important detail was ordering: the target cache must be restored after rust-cache. If the target cache is restored before rust-cache, target cleanup can remove the workspace artifacts needed for no-op behavior.
Concrete Result
Before the workaround:
- Most matrix jobs were fast, but three jobs repeatedly rebuilt local workspace crates despite full cache hits.
- The slow jobs were around 50 to 65 seconds.
- The repeated rebuilds included generated-code workspace crates and downstream binaries.
After the workaround:
- The same previously slow jobs had exact target-cache hits.
- Cargo produced no
Compiling lines for those jobs.
- Their
cargo build phase finished in approximately 0.24 to 0.31 seconds.
- The whole matrix settled into a consistent 34 to 37 second range, dominated by runner/tool setup and artifact upload rather than Rust compilation.
Proposed Feature
It would be useful if rust-cache could support source-keyed target caching directly, while preserving its current Cargo home/dependency cache behavior.
Possible APIs:
with:
cache-targets: true
cache-workspace-crates: true
target-key: ${{ steps.source-key.outputs.hash }}
or:
with:
cache-targets: true
cache-workspace-crates: true
target-key-files: |
app/**
or a more explicit split:
with:
cache-cargo-home: true
cache-targets: true
cache-workspace-crates: true
target-shared-key: one-binary
target-key: ${{ steps.source-key.outputs.hash }}
Desired behavior:
- Cargo home cache can keep using the current Rust/environment/lockfile/manifest key strategy.
- Target cache can include a user-provided source fingerprint.
- On source changes, restore from the previous target cache via restore prefix, then save the rebuilt target under the new source-keyed target cache.
- On repeated same-source runs, restore exact target cache and no-op.
Why This Would Help
Today, users who need this behavior have to compose rust-cache with a separate actions/cache target step and be careful about ordering. It works, but the interaction is subtle:
rust-cache must have cache-targets: false.
- The full target cache must restore after
rust-cache.
- The target cache key needs to include source state.
- The target cache still needs restore-prefix behavior so changed source can reuse previous dependency artifacts.
Having this built into rust-cache would make workspace-crate target caching much easier to configure correctly.
Summary
rust-cacheworks very well for dependency caching, but I ran into a repeated rebuild case when usingcache-workspace-crates: truefor a Rust workspace with multiple binary targets.The short version: the target cache key appears to intentionally ignore workspace source contents. That is normally sensible, but when workspace crate artifacts are cached, an exact cache hit can restore stale workspace artifacts and then skip saving the rebuilt target state. The next run restores the same stale target cache again, so the same workspace crates rebuild repeatedly.
It would be useful to have an option to make the target cache key include a user-provided source fingerprint, while keeping Cargo home/dependency cache behavior unchanged.
Use Case
I have a Rust workspace with multiple binary targets built in a CI matrix. Each matrix job uses a separate
CARGO_TARGET_DIR.The goal is for unchanged matrix jobs to be true Cargo no-ops across repeated CI runs, including local workspace crates.
The workflow shape is roughly:
Observed Behavior
Some matrix jobs repeatedly rebuilt local workspace crates even though the cache restore was a full match and checkout had preserved source mtimes.
The repeated rebuilds were most visible for workspace crates that have build scripts or generated code, for example:
build.rsgenerates parser code.build.rsreads a JSON file and writes generated Rust intoOUT_DIR.I added explicit build-script invalidation hints such as:
That was good for correctness, but it did not solve the repeated CI rebuild by itself.
What Appears To Be Happening
From reading the action behavior and CI logs, the issue appears to be:
rust-cacherestores an exact cache key.rust-cachepost step reportsCache up-to-date.This makes sense for dependency-only caching, but it is surprising when
cache-workspace-crates: trueis enabled and the desired behavior is to preserve workspace crate artifacts too.Workaround That Fixed It
I split the cache responsibilities:
The important detail was ordering: the target cache must be restored after
rust-cache. If the target cache is restored beforerust-cache, target cleanup can remove the workspace artifacts needed for no-op behavior.Concrete Result
Before the workaround:
After the workaround:
Compilinglines for those jobs.cargo buildphase finished in approximately 0.24 to 0.31 seconds.Proposed Feature
It would be useful if
rust-cachecould support source-keyed target caching directly, while preserving its current Cargo home/dependency cache behavior.Possible APIs:
or:
or a more explicit split:
Desired behavior:
Why This Would Help
Today, users who need this behavior have to compose
rust-cachewith a separateactions/cachetarget step and be careful about ordering. It works, but the interaction is subtle:rust-cachemust havecache-targets: false.rust-cache.Having this built into
rust-cachewould make workspace-crate target caching much easier to configure correctly.