Version ranges are convenient but not reproducible on their own. Conan resolves a range to the latest version inside it, and a copy already in your local cache can win over a newer remote one. Use ranges for flexibility, then add a lockfile so builds repeat.
How a range resolves
A range goes in square brackets after the package name. The Conan documentation gives these forms:
pkg/[>=1.0 <2.0] 1.0 up to but not including 2.0
pkg/[~2.5] 2.5.x versions
pkg/[^1.2] changes after the first non-zero digit
pkg/[1.2.3.*] versions starting with 1.2.3 (experimental)
Conan selects the latest version in the range. Versions compare digit by digit from left to right, with numbers compared numerically. Pre-releases are excluded unless you ask for them.
Why that is a reproducibility problem
Two things can change the answer without any edit to your recipe.
First, new releases. The documentation's own example shows the same install resolving to 1.0, then 1.1, then 1.2 as versions were published.
Second, the local cache. Versions already in the cache are preferred over remote ones, even when a newer remote version exists. An update flag makes Conan check the remotes. So a developer laptop and a clean CI runner can disagree about which library they used.
Security angle
Ranges cut both ways. A range can pick up a patched release automatically, which is useful. It can also pull a new, unreviewed release into a build with no change in version control. For a library that parses untrusted input, such as an image or compression library, many teams prefer to see every change in a diff.
A useful rule of thumb:
- Ranges in library recipes you publish: acceptable, because consumers need room to resolve conflicts.
- Exact versions or a lockfile in the application at the top of the graph: preferred, because that is what you ship and must be able to reproduce.
What to record when something breaks
When a build misbehaves after a range moved, you need to know which exact version it used. Conan prints the resolved graph with each requirement and its revision, for example a name, version and a revision hash. Save that output as a CI artifact for every release build. It costs nothing and answers the question "what did we actually ship?" months later, when an advisory arrives and the recipe no longer says.
A practical setup
- Keep ranges narrow, ideally within a single major version.
- Create a lockfile with
conan lock create .and commit it. - Run CI with the lockfile passed explicitly.
- Build clean in CI so a stale cache cannot influence resolution.
- Update the lockfile on a schedule and on advisories, and review the diff.
See how to pin C and C++ dependencies with a Conan lockfile for the commands.
Quick check
Can the same range give different results on two machines?
Yes. Local cache contents and newly published releases can both change what a range resolves to.
Should I ban ranges completely?
Not necessarily. Keep them narrow and pair them with a lockfile in the application you ship.
Safeguard's SBOM and dependency scanning can build an inventory of the components in a repository, and the free scan at gold.safeguard.sh/scan is a quick way to see what turns up.
Sources
Last reviewed: 19 October 2026.