---
title: Version numbers in the age of AI
author: Martin May
date: 2026-10-18
summary: We ship many times a day now, sometimes without anyone pressing the button. So what should a version number tell the people using what we make? I think the calendar has the best answer.
url: https://23made.com/thinking/version-numbers-in-the-age-of-ai/
---

# Version numbers in the age of AI

We ship a lot faster than we used to. Some days our apps and services go out twenty times, and more and more of those releases are built, tested and shipped by agents without anyone pressing a button. That got me thinking about the number at the end of all of it, the version, and whether it still means anything to the people using what we make.

I'm not alone in shipping like this. In mid-October, Claude Code was on version 2.1.302, with 74 releases in the previous 90 days, about one a day and sometimes two.[^cc] Chrome and Firefox now ship every two weeks,[^browsers] VS Code every week.[^vscode] I use all of them daily, and I couldn't tell you which version of any of them I'm on, whether it's the newest, or what changed since the last one. The number is there. It just doesn't tell me anything.

## Two kinds of software

Before going further, there's a distinction worth making, because versioning means different things on each side of it.

Some software is built on by other people: APIs, SDKs, libraries. There, the version is a contract. If you depend on 3.2, you need to know that 3.3 won't break you and that 4.0 might. [Semantic versioning](https://semver.org/) exists for exactly this, and it's still the right tool for it.

Other software is simply used by people: apps and services. What they want to know is simpler. Which one do I have? Is it current? What changed? That's the kind this post is about.

## What a version number is for

Version numbers have always been a bit of a mess. Knuth decided in 1990 that TeX's versions would converge on pi, 3.14, then 3.141, and so on; it reached 3.141592653 in 2021.[^tex] Windows 95 was internally version 4.00.950.[^win95] Semantic versioning was written down by Tom Preston-Werner in 2011,[^semver] and calendar versioning got its name and its site, calver.org, from Mahmoud Hashemi in 2016.[^calver] Chrome moved to a six-week release cycle in 2010 and Firefox followed in 2011,[^rapid] which is roughly when version numbers stopped being events. In 2025 Apple jumped from iOS 18 to iOS 26, numbering every operating system by year.[^apple]

Under all of that, a version number gets asked to answer a handful of questions. The first three are facts about a build. The last three are judgments about a change.

| Job | The question | Who asks |
| --- | --- | --- |
| Identity | Exactly which build is this? | Bug reports, support, rollbacks |
| Order | Is this newer than that? | Updaters, app stores |
| Age | When did it ship? Is mine current? | Users, support, security |
| Size of change | How big was this update? | Users, release notes |
| Safe to update | Should I update now? | Users, IT |
| Compatibility | Will it still work with my code? | Developers building on it |

## The usual schemes

There are four common ways to write a version, and each one is good at different jobs.

**Semantic versioning** is the familiar `MAJOR.MINOR.PATCH`, like `3.2.1`. You bump the major number when you break something, the minor when you add something, the patch when you fix something. It's a promise, and the spec is clear that the promise only holds if you've declared a public API.[^semver]

**Calendar versioning** uses the date. Ubuntu `24.04` shipped in April 2024. iOS 26 is the 2026 release. A version like `26.10.06` shipped on October 6, 2026. It doesn't promise anything about compatibility. It just tells you when.

**Build numbers** are counters, like `1843`, going up with every build. They're perfect for ordering within one project and meaningless outside it.

**Commit hashes** like `9f2c1e4` point at exactly one version of the code, forever. Nobody can read them, and you can't tell which of two is newer.

Put them side by side and a pattern shows. Every scheme handles the facts well, and every scheme struggles with the judgments. That's not because nobody can make those judgments. Tools can check an API for breaking changes,[^checks] and a careful rollout can tell you whether an update is safe. But someone or something has to look at the change to know, and a string of digits can't do that by itself.

**Figure 1.** Which scheme does which job, in our reading. Facts about the build sit above; judgments about the change sit below.

Which versioning scheme does which job, in our reading. Identity, order and age are facts about the build; size of change, safety and compatibility are judgments about the change.

| Job | Kind | SemVer | CalVer | Build no. | Hash |
| --- | --- | --- | --- | --- | --- |
| Identity | fact | partly | partly | does it | does it |
| Order | fact | does it | does it | does it | doesn't |
| Age | fact | doesn't | does it | doesn't | doesn't |
| Size of change | judgment | partly | doesn't | doesn't | doesn't |
| Safe to update | judgment | partly | partly | doesn't | doesn't |
| Compatibility | judgment | does it | doesn't | doesn't | doesn't |

## When nobody decides

That matters more than it used to. A patch number assumes someone looked at the change and decided: this is a fix, not a feature. When builds ship many times a day, mostly on their own, nobody makes that call. The third number just counts. That's what 302 in Claude Code's 2.1.302 is: not 302 decisions, a counter.

**Figure 2.** Our releases on October 6, 2026, in UTC. Point at one to see its version. Nobody decided whether any of them was a feature or a fix.

Our releases on October 6, 2026 (UTC), 26 in all: 00:47 iOS app, 01:53 backend, 02:13 backend, 02:20 backend, 03:03 iOS app, 03:10 backend, 06:24 backend, 06:29 iOS app, 07:42 backend, 08:42 backend, 09:17 iOS app, 09:43 backend, 10:45 backend, 13:32 iOS app, 14:16 backend, 15:32 backend, 16:02 backend, 17:06 backend, 17:42 backend, 18:21 backend, 18:26 iOS app, 19:00 backend, 20:01 iOS app, 20:56 backend, 23:09 iOS app, 23:21 backend.

There's a second wrinkle that's new. Software built on AI models can change behavior without changing its version at all, because the model underneath it changed. Model providers say so openly; OpenAI's changelog notes when a "latest" name gets pointed at a newer model.[^openai] In a discussion about versioning agent skills, one contributor put the problem in a line:[^skills]

> A semver and a digest get a consumer to "this is the bundle I reviewed", not "this bundle still does what I reviewed it for".

So for software people use, the version can't carry everything it used to. The question is what it can carry honestly.

## Why the calendar

This is where calendar versions get compelling. They do the jobs a version can still do without anyone deciding anything. A date is always newer than the day before, so order comes free. You can see at a glance how old something is, and whether it's current. And there's no judgment to fake: `26.10.06` makes no claim about how big the change was, so it can't be wrong about it.

## Where the time goes

A date alone isn't enough when you ship more than once a day. The time has to go somewhere. You could keep adding fields, `26.10.06.1847`, but that gets long, and Apple only allows three numbers in an app's version anyway.[^apple-build]

So the time goes in the build number. Every build gets `YYMMDDHHMM`, the minute it was made: `2610061847` is 18:47 on October 6, 2026. The app shows it the way iOS apps usually show builds, `26.10.06 (1847)`. Every component, the iOS app and the backend alike, gets its own version and build, and whatever shipped together gets a release id.

**Figure 3.** Three stamps for one release, all in UTC.

Three stamps for one release, all in UTC:

| Component | Stamp | Format |
| --- | --- | --- |
| iOS app | 26.10.06 (2610060043), shown as "26.10.06 (0043)" | YY.MM.DD (YYMMDDHHMM) |
| Backend | 26.10.06 (2610060047) | YY.MM.DD (YYMMDDHHMM) |
| Release | 20261006T0044Z | YYYYMMDDTHHMMZ |

One catch: `2610061847` is bigger than a 32-bit integer can hold, so anything that stores builds that way will break. Apple treats the build as text, and our iOS builds have carried this format since October. Android's version code is a single integer capped at 2,100,000,000,[^android] which this format already passes, so an Android app needs its own counter.

## Which clock

My first instinct was local time. We're in Denver, a version is something people read, and it felt wrong for a release at 6:44 PM on October 5 to say October 6.

Daylight saving time changed my mind. Once a year the clock goes back an hour, and for that hour local time repeats itself. A build made at 1:10 AM after the change gets a lower number than one made at 1:50 AM before it, even though it came 20 minutes later. That breaks the one job a version absolutely has to do.

**Figure 4.** November 1, 2026, the night Denver's clocks go back. In Denver time, two later builds get lower numbers; in UTC, they never do.

Four builds 30 minutes apart on November 1, 2026, the night daylight saving time ends in Denver. In Denver time they are stamped 2611010120 (01:20 MDT), 2611010150 (01:50 MDT), 2611010110 (01:10 MST), 2611010140 (01:40 MST): the third and fourth come after the second but get lower numbers, because the clock went back an hour. In UTC they are 2611010720, 2611010750, 2611010810, 2611010840, always in order.

UTC never repeats and never skips. And the people using what we make aren't all in Denver anyway, so "local" was only ever our local. Everything is UTC now: versions, builds and release ids all agree on the date, and an evening release in Denver carries tomorrow's. That's a price I'm happy to pay.

## Two builds in one minute

Minute-level stamps have one more edge: two builds in the same minute would get the same number. Our first answer was to bump the second one to the next minute. It keeps every build unique and in order, but the numbers start drifting from the clock. In a burst of five builds, the last one is stamped four minutes after it actually shipped, and nothing warns you.

The simpler answer is to wait. If a minute already has a build, the next one holds until the minute turns, then gets stamped with the real time. Every number tells the truth, it works on every platform, and the cost is a build that ships a minute later in a rare burst. So far, one pair of builds in our first 154 releases has landed in the same minute.

**Figure 5.** A burst of builds inside one minute. "Bump the minute" ships at once and the numbers drift from the clock; "Wait for the minute" keeps every number true and holds a build briefly.

A burst of builds ready inside one minute, under two rules for a second build in the same minute. Bump the minute: builds ready at 16:00:00, 16:00:30 and 16:00:59 UTC ship at once as 2610061600, 2610061601 and 2610061602, so the last one's number claims 16:02; with N builds the last is up to N − 1 minutes off. Wait for the minute: the second build ships at 16:01:00 as 2610061601 and the third at 16:02:00 as 2610061602; every number tells the truth, and the last build waits about a minute.

## Betas, TestFlight and channels

So where do betas fit? With a build number that names one exact build, a beta stops being a different kind of version. It's a build that hasn't reached everyone yet.

TestFlight, a canary group, a "stable" channel: those are about who gets a build, not what it is. The same build can go to TestFlight, then to a few percent of users, then to everyone, without ever being renumbered. Promote it, don't rebuild it. Claude Code works a bit like this: its "stable" channel trails its latest release by about ten versions.[^cc] Labels like `-beta.2` still make sense when you rebuild for release. When you ship the exact bytes you tested, the build number is already the identity.

Two more things follow. A rollback is just shipping an older build again: the release id records that it happened, and the build keeps its number. And a calendar version gives up the big "2.0" moment. If something deserves a launch, give it a name, the way Apple names each macOS release.

## Where the meaning goes

If the version only says when and in what order, the rest has to live somewhere else. How big a change was belongs in release notes, written per build, about what changed for people, not which files changed. Who gets it belongs to the channel. Whether it's safe belongs in how you roll it out: a small group first, watch the errors, then everyone. And the promise not to break things belongs in semantic versions, on the APIs and SDKs where somebody is actually building on top of you.

**Figure 6.** Where each job goes once the version stops pretending to carry it.

Which artifact carries which job:

| Question | Carried by | Example |
| --- | --- | --- |
| When, and in what order | The version and build | 26.10.06 (0047) |
| Which bytes, exactly | A content digest | sha256:9f2c…e41a |
| How big | Release notes per build | “Replies stream while research runs” |
| Who gets it | The channel | TestFlight → 2% → everyone |
| How safe | Cohorts and a health signal | errors flat · promote |
| What we promise not to break | SemVer, on public APIs only | sdk 3.2.0 |

## What I'd do

If you make something people use and you ship it more than every couple of weeks, I'd give it a calendar version, put the time in the build, stamp both in UTC, and let a build that lands in a busy minute wait for the next one. Use channels for betas, and promote builds instead of renumbering them. Spend the effort you used to spend choosing version numbers on release notes instead. Keep semantic versioning for the things other people build on. Everywhere else, let the version tell people when, and tell them what changed in words.

[^cc]: npm registry data for [@anthropic-ai/claude-code](https://registry.npmjs.org/@anthropic-ai/claude-code), read October 18, 2026: version 2.1.302 published October 16; 74 releases in the 90 days before; the "stable" tag on 2.1.292.
[^browsers]: Chrome's [move to a two-week cycle](https://developer.chrome.com/blog/chrome-two-week-release) from Chrome 153 in September 2026, and Firefox's two-week releases since Firefox 155, per Mozilla's [release calendar](https://whattrainisitnow.com/calendar/).
[^vscode]: The [VS Code 1.111 release notes](https://code.visualstudio.com/updates/v1_111), March 2026, the first weekly release.
[^tex]: Donald Knuth, ["The Future of TeX and METAFONT"](https://tug.org/TUGboat/tb11-4/tb30knut.pdf), TUGboat 11:4, 1990; the version history is in [tex.web](https://mirrors.ctan.org/systems/knuth/dist/tex/tex.web).
[^win95]: [Windows 95](https://en.wikipedia.org/wiki/Windows_95), version 4.00.950.
[^semver]: [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html): "Software using Semantic Versioning MUST declare a public API." Version 1.0.0 was tagged in 2011 and 2.0.0 in 2013 ([history](https://github.com/semver/semver)).
[^calver]: Mahmoud Hashemi, ["Designing a version"](https://sedimental.org/designing_a_version.html), February 2016, and [calver.org](https://calver.org/).
[^rapid]: The Chromium blog, ["Release early, release often"](https://blog.chromium.org/2010/07/release-early-release-often.html), July 2010, and Mozilla, ["Every six weeks"](https://blog.mozilla.org/futurereleases/2011/07/19/every-six-weeks/), July 2011.
[^apple]: Apple Newsroom, [June 2025](https://www.apple.com/newsroom/2025/06/macos-tahoe-26-makes-the-mac-more-capable-productive-and-intelligent-than-ever/).
[^checks]: For example [cargo-semver-checks](https://github.com/obi1kenobi/cargo-semver-checks), which compares two versions of a Rust crate's public API.
[^openai]: The [OpenAI API changelog](https://developers.openai.com/api/docs/changelog).
[^skills]: In the Agent Skills project's [discussion of versioning](https://github.com/agentskills/agentskills/issues/46#issuecomment-4836608765).
[^apple-build]: Apple's [CFBundleShortVersionString](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleshortversionstring) is three period-separated integers; [CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleversion), the build, is one to three.
[^android]: Android's [versionCode](https://developer.android.com/studio/publish/versioning), whose largest allowed value is 2,100,000,000.
