# Default version bound constraints

When you run `npm add X`, `cargo add X`, `poetry add X`, or `uv add X`, the tool picks the version constraint that will end up in your manifest. That default is a small decision with long-running consequences, because most projects accept whatever the tool wrote and never revisit it.

The two camps:

| Ecosystem | Default for `add X` (current version 1.2.3) | Upper bound? |
|-----------|---------------------------------------------|--------------|
| npm / pnpm / yarn | `^1.2.3` (caret = compatible major) | Yes — implicit at major-version line |
| Cargo (Rust) | `1.2.3` (caret implied; same as `^1.2.3`) | Yes |
| Poetry (Python) | `>=1.2.3,<2.0.0` (or `^1.2.3` in newer versions) | Yes |
| Bundler (Ruby) | `~> 1.2` (pessimistic constraint) | Yes |
| uv (Python) | `>=1.2.3` | **No** |
| pip-tools (Python) | exact pin from `compile` | Yes (over-strict) |

The case for an implicit upper bound: SemVer says a major-version bump is allowed to break public API, so a tool that doesn't pin the major-version line is opting every project into eventual breakage at `update`/`upgrade` time. The case for no upper bound (uv's choice): SemVer is aspirational not enforced, upper bounds cause resolver pain when a dependency releases a major bump that doesn't actually break you, and Python in particular has a long history of unhelpful upper-bound conflicts (`poetry add` famously refused to install many libraries because of transitive upper bounds the libraries didn't really need).

The pain shows up at different times:

- With upper bounds: more frequent "the resolver can't satisfy this" errors, more frequent need to bump the pin manually.
- Without upper bounds: silent acceptance of breaking changes during routine `upgrade` runs, with the failure mode being broken builds days or weeks later when a transitive bump turns out to have removed something.

uv currently sits in the without-upper-bounds camp by default and offers `--bounds major` as a preview opt-in. The argument in [[loopwerk-uv-ux-mess]] is that the unsafe direction is the wrong default — most users won't audit lockfile diffs carefully enough to catch a major-version bump landing through a transitive dependency, and the cost of an over-tight constraint (a quick manual bump) is much cheaper than the cost of a silent break (an outage you have to bisect).

This is also a story about who absorbs the cost. Library publishers prefer no upper bounds on their dependencies because it avoids the version-floor explosion problem. Application authors prefer upper bounds because they're the ones who have to deal with the broken builds. Default constraint behavior is the package manager taking a side in that conflict.

## See also

- [[loopwerk-uv-ux-mess]] — Kevin Renskers's specific case against uv's default
- [[supply-chain-security]] — broader supply-chain considerations; cooldowns ([[open-source-security-astral]]) are an orthogonal mitigation
