cargo-cooldown is a Cargo wrapper that delays adoption of freshly published
registry crate versions. It lets Cargo resolve the graph, then replaces fresh
versions with the newest older compatible versions that Cargo still accepts.
Use it when you want dependency updates, but do not want Cargo.lock to pick
up releases that were published too recently.
Install:
cargo install --locked cargo-cooldownFor CI jobs that only need the cargo-cooldown binary, install from the
project's prebuilt GitHub release artifacts with cargo-binstall:
cargo install cargo-binstall --locked
cargo binstall cargo-cooldown --no-confirmReleases include SHA256SUMS and GitHub Artifact Attestations. After
downloading an archive from a release, verify its provenance with:
gh attestation verify cargo-cooldown-x86_64-unknown-linux-gnu-vX.Y.Z.tgz \
-R dertin/cargo-cooldownCreate a project config:
cargo cooldown initRun a Cargo command through cooldown:
cargo cooldown checkcheck, build, test, and run are guard-style commands: cooldown runs a
pre-command Cargo.lock cooldown pass before Cargo downloads, compiles, tests,
or runs code from the dependency graph. With the default
lockfile-baseline = "floor" under [cooldown], versions already present in
the initial Cargo.lock are treated as the protected baseline; use
lockfile-baseline = "ignore" under [cooldown] when these commands should
also try to cool already-locked versions before Cargo consumes them.
Update dependencies under cooldown:
cargo cooldown updateupdate is the Cargo.lock refresh command: Cargo resolves the newest graph
first, then cooldown cools the updated Cargo.lock before it is kept.
cargo cooldown init is cargo-cooldown's setup wizard. To create a new Cargo
package, use Cargo's own command:
cargo initcooldown.toml usually starts with:
[cooldown]
incompatible-publish-age = "deny"
lockfile-baseline = "floor"
[registry]
global-min-publish-age = "14 days"Meaning:
[registry].global-min-publish-age: how old a release must be before cooldown accepts it[cooldown].incompatible-publish-age: what to do if Cargo still requires fresh versions[cooldown].lockfile-baseline: whether the initialCargo.lockis used as a version floor
Config is loaded in this order, from strongest to weakest:
- environment variables
- active member
cooldown.toml, when exactly one workspace member is targeted - workspace or crate root
cooldown.toml $CARGO_HOME/cooldown.toml
These settings answer different questions:
[cooldown].lockfile-baselinecontrols whether cooldown may go below versions already present in the initialCargo.lock.[cooldown].incompatible-publish-agecontrols what happens if Cargo still requires fresh versions.
lockfile-baseline = "ignore" is not a force setting. Cargo still validates
the final graph, so cooldown never writes a Cargo.lock that Cargo rejects.
| Configuration | Meaning |
|---|---|
lockfile-baseline = "floor" + incompatible-publish-age = "deny" |
cargo cooldown init default and RFC-aligned fail-closed policy. Use the pre-run Cargo.lock as the minimum version floor. If any new fresh version remains, fail and restore the original Cargo.lock. |
lockfile-baseline = "floor" + incompatible-publish-age = "fallback" |
Use the pre-run Cargo.lock as the minimum version floor, keep the best Cargo.lock that Cargo accepts if some fresh versions remain, and warn. Useful for long min-publish-age windows or benchmark runs. |
lockfile-baseline = "ignore" + incompatible-publish-age = "deny" |
Try to cool every eligible locked registry package, including versions already present before the run. If any fresh version still cannot be cooled, fail and restore the original Cargo.lock. |
lockfile-baseline = "ignore" + incompatible-publish-age = "fallback" |
Most permissive update policy. Try to cool everything, keep the best Cargo.lock that Cargo accepts, and warn about any remaining fresh versions. |
A fresh version can remain when the current Cargo.toml graph requires it. That
can happen because of semver ranges, exact dependencies, feature-selected
dependencies, target-specific dependencies, or a group of crates that does not
have an older compatible combination.
incompatible-publish-age = "fallback" is flexible only where Cargo
requires a fresh version. It still cools every package Cargo can accept and
reports the remaining fresh versions so you can review the supply-chain risk.
By default, fallback asks before accepting unresolved fresh versions.
Use fallback-accept = "auto" only when you want the previous
non-interactive behavior.
Use cargo-cooldown's own variables for policy overrides:
COOLDOWN_INCOMPATIBLE_PUBLISH_AGE=fallback
COOLDOWN_FALLBACK_ACCEPT=promptAllow rules live in cooldown.toml:
[[allow.exact]]
crate = "serde"
version = "1.0.218"
[[allow.package]]
crate = "tokio"
min-publish-age = "1 hour"
[[allow.package]]
crate = "openssl"
min-publish-age = "0"Use:
[[allow.exact]]to allow one exact crate version[[allow.package]]to use a shorter cooldown for one cratemin-publish-age = "0"to exclude one crate from cooldown
Allow rules only reduce the effective cooldown window. They do not make a crate wait longer than the configured min publish age.
Cargo's registry configuration is the source of truth. cargo-cooldown reads
release timestamps from the local registry index first and uses registry HTTP
fallback only when local pubtime is missing.
Skip a registry completely:
skip_registries = ["crates-io", "sparse+https://example.com/index/"]Skipped registries are not inspected or downgraded, but their packages still shape Cargo's dependency graph.
Set registry-specific min publish ages in cooldown.toml without reading
Cargo's config files as policy:
[registry]
global-min-publish-age = "14 days"
min-publish-age = "5 days"
[registries.internal]
index = "sparse+https://example.com/index/"
min-publish-age = "0"index is optional. When present, it matches the effective registry index URL;
otherwise cargo-cooldown resolves the registry name through the Cargo registry
configuration Cargo already uses. Duration values accept 0 or N seconds,
minutes, hours, days, weeks, or months; months means fixed 30-day
months.
Recommended layout:
- one shared
cooldown.tomlat the workspace root - optional
member/cooldown.tomloverrides only for member-specific runs
Member overrides apply only when the command targets exactly one member.
examples/crates-io-smoke-workspace/: small crates.io-backed smoke workspaceexamples/crates-io-large-benchmark-workspace/: larger benchmark workspaceexamples/run-crates-io-smoke.sh: manual smoke checksexamples/run-crates-io-benchmark.sh: shared benchmark runner
Run the large benchmark:
cargo bench --bench crates_io_cooldown -- --scenario large-60dcargo-cooldown is intended for local development workflows where you refresh
dependencies and build immediately. CI pipelines and release automation should
usually use plain Cargo against committed Cargo.lock files.