Below 1.0, the minor number is the breaking one
Cargo and npm treat ^0.3.4 as any 0.3.y. Ship a breaking change as 0.3.5 and it lands in everyone's next build unasked. So we wrote the rule down.

Foxstash 0.9 removed a public storage mode. Anyone whose code named it gets a compile error on upgrade. That's a breaking change, so the release is 0.9.0, not 0.8.1.
That sounds obvious. It wasn't written down anywhere until this week, and the reason it matters below 1.0 is not the reason most people think.
What the caret actually does
When you add a dependency in Rust, cargo add writes something like foxstash-core = "0.8.0",
which Cargo reads as ^0.8.0. npm writes "^0.8.0" outright. Both mean "this version, plus anything compatible with it". And both decide
compatibility the same way:
| You wrote | Accepts | Refuses |
|---|---|---|
^1.3.4 |
1.3.5, 1.4.0, 1.99.0 |
2.0.0 |
^0.3.4 |
0.3.5, 0.3.99 |
0.4.0 |
Below 1.0, the caret treats the second number as the one that breaks things. So a dependent pinned
to ^0.3.4 picks up every 0.3.y the next time anyone runs an update or builds without a
lockfile, and refuses 0.4.0 until a person chooses it.
That makes the version number a promise. Ship a breaking change as 0.3.5 and it lands in every
dependent's next build, unasked, and breaks it. Ship it as 0.4.0 and it waits until they opt in.
The number is the only signal they get.
The rule
Semantic versioning, shifted one place while the major version is zero:
| Change | Before 1.0.0 (0.x.y) |
From 1.0.0 (x.y.z) |
|---|---|---|
Breaking (! or BREAKING CHANGE:) |
x: 0.3.4 to 0.4.0 |
x: 1.3.4 to 2.0.0 |
New feature (feat) |
y: 0.3.4 to 0.3.5 |
y: 1.3.4 to 1.4.0 |
Fix or other released change (fix, perf) |
y: 0.3.4 to 0.3.5 |
z: 1.3.4 to 1.3.5 |
Below 1.0 there's no separate slot for features, so features and fixes share the last number. That loses a little information. It's the price of keeping the one signal that matters, "this will break you", in the position the tools actually read.
Two more lines go with the table.
Going to 1.0 is a decision, not a counter. It means the public API is stable. It shouldn't happen because a run of features rolled the number over.
Tell your release tool. By default, release-please treats a breaking change below 1.0 as a
jump straight to 1.0.0, and bumps the minor on every feature, which under this rule marks every
feature as breaking. Set bump-minor-pre-major and bump-patch-for-minor-pre-major to true and
it follows the table.
Where it lives now
My company's software lifecycle procedure now has this table in its release stage, and a release isn't done until the version bump matches the type of change. The yearly internal audit checks it: for each sampled release, compare the commits since the last tag, and if any were breaking, the minor number had better have moved. The same table sits in the knowledge base my build machines read, word for word, because those machines can't see the company drive where the procedure lives.
Foxstash 0.9.0 was the first release after the rule was written down. It's breaking, it bumped the minor, and anyone
on foxstash-core = "0.8" stays on 0.8 until they decide to move.
What I'd tell you if you were buying this work
If you depend on a library that's still below 1.0, read its changelog before each minor upgrade, and check whether the maintainers treat the minor number as the breaking one. If you're paying someone to ship a library to you, ask how they number releases and what stops a breaking change from going out as a patch. "We're careful" isn't an answer. A rule someone audits is.
About the author
- Founder of Narcoleptic Fox. Marine veteran. Senior engineer learning AI/ML and writing about it. Building Mousing.ai, Reqforay,.ai, and foxstash.
Comments
Join the discussionNo comments yet. Be the first to comment.
Foxstash
Foxstash is my open-source Rust library for local vector, keyword and hybrid search, embedded in your application with no server. Releases, benchmarks, and the mistakes caught along the way.




