dependencies · Sep 19, 2026
Why We Pinned Tidewrangle at 4.2.1
A minor release changed how a date library handled timezones, and our invoices shifted by a day. What a caret range actually allows, and the rules we use to pin on purpose.
Tidewrangle is a small date library, used in forty places, trusted by everyone and read by no one. Version 4.3.0 was a minor release with a changelog line that said "improved timezone defaults". Our invoices, which are generated at midnight UTC, started landing on the wrong day for customers in two time zones.
Nothing broke loudly. The tests passed. The build was green. The first signal was a customer asking why their invoice was dated tomorrow.
What the range allowed
Our manifest said ^4.2.1. Most of us read that as "4.2.1, and bug fixes". It means more than that, and I'd rather show it than assert it. I asked the semver tool which of four versions each range lets in:
pnpm dlx semver@7 -r '^4.2.1' 4.2.0 4.2.9 4.3.0 5.0.0
pnpm dlx semver@7 -r '~4.2.1' 4.2.0 4.2.9 4.3.0 5.0.0
pnpm dlx semver@7 -r '4.2.1' 4.2.0 4.2.1 4.2.9The caret printed 4.2.9 and 4.3.0. The tilde printed 4.2.9 only. The bare version printed 4.2.1 and nothing else. So the caret that we trusted for patches also admits every future minor release, and a minor release is allowed to change behaviour as long as the author considers the change compatible. The author's idea of compatible and ours were a day apart.
The timeline
Mon 06:10: a scheduled dependency refresh lands 4.3.0 in the lockfile
Mon 06:40: tests and build pass; the change merges as "chore"
Tue 00:00: first invoices of the month generate with the new defaults
Tue 09:15: a customer reports a tomorrow-dated invoice
Tue 10:30: bisecting the lockfile points at 4.3.0
Tue 11:05: pinned back to 4.2.1, affected invoices regenerated
Eleven hours from the first wrong invoice to the fix, and the only reason it wasn't longer is that the customer wrote in politely.
Our rules for pinning
We didn't conclude that ranges are bad. Most dependencies are fine on a caret, and updating them by hand would be worse. We drew a line:
Pin exactly when the library touches money, dates, identity or serialization. If a wrong answer is quiet, the version is ours to move.
Everything else keeps its caret, and the lockfile makes sure nothing changes until a human merges the update.
A pin carries a comment naming the reason and the condition for removing it.
The third one matters most. A pin with no explanation becomes folklore within a year. Ours reads: pinned at 4.2.1 until the timezone default is covered by a test of our own.
What the test looks like now
We wrote the test we should have had: one invoice, generated at 23:59 and 00:01 UTC for customers in a zone far east and one far west, with the expected dates written out by hand. It runs against whatever version is installed, so the next minor release gets examined before it gets trusted.
No comments yet
Comments are open. Have a thought or a question? Share it below.