- Published on
Don't put a timestamp in the dependency cache key
- Authors

- Name
- Mehdi Akiki
Article · Derived state
The workflow saves a dependency cache after every successful run. Its only restore key contains that run's start time. The next run asks for a different key and restores nothing.
The cache is working. The workflow has no useful way to find an earlier entry.
I would separate inputs that affect reuse from identifiers that only describe a run. A timestamp belongs in a log or artifact name when it helps people. It usually does not belong in the identity of unchanged dependencies.
Describe the reusable state
For a Rust build cache, consider the toolchain, target, profile, relevant flags, and dependency graph. If native dependencies are involved, the operating environment matters too.
A conceptual key might have this shape:
rust-deps-v2 / runner-image / toolchain / target / profile / lockfile-hash
This is a design sketch, not a complete GitHub Actions configuration. The exact inputs depend on what paths the workflow caches. A Cargo registry download cache and compiled target artifacts have different compatibility requirements.
GitHub documents exact and prefix cache matching. A suitable restore prefix can find useful material when the full key changes. It must still respect the toolchain, target, and other compatibility requirements of the files being restored.
Cargo still needs to inspect its inputs and rebuild what is dirty. The incremental-cache article explains that layer, while the fingerprint guide helps investigate unexpected rebuilds.
Keep correctness inputs out of the blind spot
Removing a timestamp from a dependency key does not mean time can never affect a build. A static site that includes scheduled content may legitimately need a fresh final build when a publication date arrives.
Reuse dependencies where their inputs allow it. Regenerate final output when the final output's inputs change. Do not restore a whole old site and assume a successful dependency-cache hit made today's pages correct.
The same distinction applies to generated files, environment-dependent build scripts, and external schemas. Undeclared inputs are a correctness bug even if the cache makes the workflow fast.
Also keep secrets out of cached paths. A cache is storage with its own access rules, not a private secret manager.
Verify with comparable workflow runs
Use the existing CI workflow to compare two runs with unchanged dependencies. Record the cache key, restore result, and whether Cargo reused the expected work. Then change one relevant input and confirm the build still produces the correct result.
If builds remain slow after a cache hit, inspect the actual phase. Compilation, linking, generation, and tests can dominate for different reasons. Do not keep changing the key because one total duration looks disappointing.
Cache storage is finite. More detailed keys can create more entries, while overly broad keys can restore little useful state. Choose the scope based on evidence from representative changes.
When a changing suffix is intentional
GitHub cache entries cannot be updated in place. A workflow can deliberately restore through a stable compatible prefix and save a new entry with a run-specific suffix. That lets a growing compiled-artifact cache include work from the latest run. A timestamp is not automatically wrong in that design.
The failure to avoid is adding a changing suffix while relying only on exact matches. Choose the restore and save strategy together, and inspect whether the restored files actually reduce work. A cache-hit flag alone cannot show that.
What the dependency cache does in this repository
The workflow uses Swatinem's Rust cache with stable workspace paths. The PostgreSQL examples and the backend lab have separate target directories, so both are declared. The configuration does not append the current time or the commit identifier to a dependency-only cache key.
That does not mean every run restores the same bytes. The action derives parts of its key from the Rust toolchain and dependency inputs. Its documented key inputs include manifests, lockfiles, compiler identity, and relevant environment settings. Review those inputs before adding another broad manual key. An extra key can accidentally prevent reuse the action already supports.
There is a useful limit to the evidence here. Several development runs passed the actual examples and failed the final formatting gate. A failed job is not proof that its dependency state was saved for the next job. The action's save policy matters. Nor is a successful restore message proof that the restored graph matches a changed feature selection. Keep the locked build as the correctness check.
For a real hit-rate comparison, retain the restore log, resolved key, save result, and dependency compilation log from two comparable successful runs. Compare the same workspace and runner image. Separate download time from dependency compilation and application compilation. A faster overall job may simply have spent less time waiting for a package registry.
Do not turn a cache investigation into an unrestricted dependency update. In this repository the database jobs fetch and test with --locked. A deliberate dependency change belongs in a reviewed lockfile diff. The cache can avoid repeating work; it does not choose the software version the article's example is supposed to test.
The practical decision is small: keep stable compatibility inputs in the dependency key, declare the actual target directories, and use logs to decide whether reuse occurred. If a unique artifact is needed for every run, give that artifact a unique name without making the reusable dependency cache unique too.