●2.1.285 — Claude Code 2.1.285 (Sep 29): set CLAUDE_CODE_DISABLE_WEB_FETCH to turn off WebFetch, and run claude --desktop to open the current directory in the desktop app●SONNET5.5 — With 2.1.284, claude-sonnet-5-5 is the default Sonnet on the API: 1M context, $2 / $10 per Mtok, $0.20 cache reads●10/07 — 7 days left until the old spellings of Claude Desktop / Cowork managed-config keys stop being accepted. After the cutoff they fail closed●WINDOWS — Two Windows questions keep coming up in Issues: the desktop app won't relaunch, and it stays on top of every other window. Check for an orphaned process before touching settings●NEW — The stable Claude Code channel still hasn't received two default-model changes●BING — 1,055 Bing sessions in 28 days, up four weeks running. Readers land on billing errors, the Academic plan, Figma MCP and PowerShell●2.1.285 — Claude Code 2.1.285 (Sep 29): set CLAUDE_CODE_DISABLE_WEB_FETCH to turn off WebFetch, and run claude --desktop to open the current directory in the desktop app●SONNET5.5 — With 2.1.284, claude-sonnet-5-5 is the default Sonnet on the API: 1M context, $2 / $10 per Mtok, $0.20 cache reads●10/07 — 7 days left until the old spellings of Claude Desktop / Cowork managed-config keys stop being accepted. After the cutoff they fail closed●WINDOWS — Two Windows questions keep coming up in Issues: the desktop app won't relaunch, and it stays on top of every other window. Check for an orphaned process before touching settings●NEW — The stable Claude Code channel still hasn't received two default-model changes●BING — 1,055 Bing sessions in 28 days, up four weeks running. Readers land on billing errors, the Academic plan, Figma MCP and PowerShell
Two Default-Model Changes Haven't Reached Claude Code's Stable Channel Yet
This morning, Claude Code's stable channel sat 6 releases and 10 days behind latest. A small standard-library Python script that counts the gap in real, published releases instead of version arithmetic, and lists what hasn't arrived yet with a headline for each.
Tuesday mornings start with a small chore on the machines I keep running: I write down the Claude Code version, one line per day, so I can later trace what arrived when. This morning the line for latest read 2.1.284. Then I checked stable the same way, and the answer was 2.1.277 — unchanged since September 18.
In those ten days the default Opus moved to 5.5, and yesterday's release moved the default Sonnet to 5.5 as well. A machine on the stable channel has received neither. That was the moment I realized I had been trusting the phrase "stable is about a week behind" without ever counting it.
The counting tool came to about 80 lines of standard-library Python. What follows is how I built it, together with the numbers it produced today. If you can run python3 in a terminal, you can follow along even if you don't write code often.
The numbers first
The first thing I'd like to say plainly: the stable channel's lag should be measured in real, published releases, not described as "about a week." Here is what I measured this morning.
Channel
Points to
Published (UTC)
stable
2.1.277
2026-09-18
latest
2.1.284
2026-09-28
Gap
6 releases
10 days
The 6 deserves a footnote. Subtract 277 from 284 and you get 7, but there is no 2.1.279 in the npm registry. Arithmetic says 7; counting the versions that exist says 6. That mismatch ended up shaping the script's design.
Taking only the first bullet of each release note, these are the changes a stable-channel machine does not yet have:
Version
Published
First listed change
2.1.278
Sep 19
Auto mode's classifier defaults to the server side for API and Enterprise users and on Bedrock / Vertex / Foundry / gateways (no classifier overhead billed)
2.1.280
Sep 22
Claude Opus 5.5 (claude-opus-5-5) becomes the default Opus model: 1M context, $4/$20 per Mtok, $0.20 cache reads
2.1.281
Sep 23
Claude apps gateway support for newer Claude Desktop policy keys
2.1.282
Sep 24
New maxProseWidth setting
2.1.283
Sep 25
x-claude-code-prompt-id added to the gateway hint headers
2.1.284
Sep 28
Claude Sonnet 5.5 (claude-sonnet-5-5) becomes the default Sonnet model: 1M context, $2/$10 per Mtok, $0.20 cache reads
Two default-model changes and one billing-relevant default change. I still think choosing the stable channel is a sound decision. But if you know, in one line each morning, what that choice is holding back, you get to decide for yourself whether to wait or to move.
Step 1: Find out which channel your machine follows
Before building anything, confirm where your own installation stands. There are three places to look.
# the version that is runningclaude --version# the channel you follow (defaults to latest if the key is absent)grep -H autoUpdatesChannel ~/.claude/settings.json 2>/dev/null || echo "autoUpdatesChannel not set (latest)"# native installs keep their binaries herels ~/.local/share/claude/versions/ 2>/dev/null
Inside a session, opening /config and reading the Auto-update channel row is enough. The documentation describes latest, the default, as receiving new features as soon as they ship, and stable as a version that is typically about one week old, skipping releases with major regressions.
One caveat. Homebrew users pick a channel by cask name rather than by this setting: claude-code tracks stable and claude-code@latest tracks latest. For npm installs, the docs note that npm update -g may not move you to the newest release, so npm install -g @anthropic-ai/claude-code@latest is the safer way to upgrade.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You will be able to check, every morning with one script, whether your Claude Code follows the stable or latest channel and how many releases behind it currently sits
✦You will be able to count only the releases that actually exist, instead of subtracting version numbers, and turn the result into a headline list of changes that haven't reached you
✦You will be able to avoid spending weeks unaware that a default-model change or an auto-mode default change never arrived in your environment
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The response is plain text: just the version number. I ran these two lines by hand and looked at the output before touching the script. That is a rule I keep for myself — never call an endpoint for the first time from inside a script. A parser written against a response shape you haven't seen can't tell the day it works from the day it breaks.
The npm registry agrees. Its dist-tags list stable and latest, and this morning they read 2.1.277 and 2.1.284. The registry also carries the publish timestamp of every version under time, which is what I borrow for the day count.
Step 3: Write the script
Here is the whole thing. No third-party packages. Release-note headlines from GitHub are attached when available, but the version gap is printed even when they aren't.
#!/usr/bin/env python3"""claude_channel_gap.pyCounts the gap between the stable and latest channels in real, publishedreleases, and lists the changes that haven't reached stable yet.Standard library only."""import jsonimport reimport subprocessimport sysimport urllib.requestfrom datetime import datetimeCHANNEL_URL = "https://downloads.claude.ai/claude-code-releases/{}" # stable / latest, one line eachNPM_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code" # borrowed for publish timestamps (time)GH_URL = "https://api.github.com/repos/anthropics/claude-code/releases?per_page=30"def fetch(url, timeout=15): # GitHub's API may return 403 without a User-Agent, so always send one req = urllib.request.Request(url, headers={"User-Agent": "claude-channel-gap/1.0"}) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read().decode("utf-8")def vkey(v): # "2.1.284" -> (2, 1, 284). As strings, "2.1.99" would sort above "2.1.284" return tuple(int(x) for x in v.split("."))def installed_version(): # return None on machines without claude so the script still runs try: out = subprocess.run(["claude", "--version"], capture_output=True, text=True, timeout=10).stdout m = re.search(r"\d+\.\d+\.\d+", out) return m.group(0) if m else None except (FileNotFoundError, subprocess.TimeoutExpired): return Nonedef main(): quiet = "--quiet" in sys.argv stable = fetch(CHANNEL_URL.format("stable")).strip() latest = fetch(CHANNEL_URL.format("latest")).strip() npm = json.loads(fetch(NPM_URL)) times = npm["time"] # keep only real x.y.z versions (drops pre-releases such as 2.1.284-beta), ascending versions = sorted((v for v in npm["versions"] if re.fullmatch(r"\d+\.\d+\.\d+", v)), key=vkey) between = [v for v in versions if vkey(stable) < vkey(v) <= vkey(latest)] def published(v): return datetime.fromisoformat(times[v].replace("Z", "+00:00")) lag_days = (published(latest) - published(stable)).days arithmetic = vkey(latest)[2] - vkey(stable)[2] # what subtraction says; can differ from the real count notes = {} try: for r in json.loads(fetch(GH_URL)): v = r["tag_name"].lstrip("v") body = r.get("body") or "" first = next((l[2:].strip() for l in body.splitlines() if l.startswith("- ")), "") notes[v] = first except Exception as e: # hitting the 60/hour limit must not hide the version gap print(f"(release notes skipped: {e})", file=sys.stderr) me = installed_version() if quiet: mine = f" installed={me}" if me else "" print(f"stable={stable} latest={latest} gap={len(between)}rel/{lag_days}d{mine}") return print(f"stable {stable} ({times[stable][:10]})") print(f"latest {latest} ({times[latest][:10]})") print(f"gap {len(between)} releases / {lag_days} days (arithmetic says {arithmetic})") if me: behind = [v for v in versions if vkey(me) < vkey(v) <= vkey(latest)] print(f"installed {me}: {len(behind)} releases behind latest") print("not yet on stable:") for v in between: print(f" {v} {times[v][:10]} {notes.get(v, '(release notes not published yet)')[:100]}")if __name__ == "__main__": main()
Three decisions are worth explaining.
First, the list of versions comes from the npm registry's versions, not from the download server. The download server only tells you where each channel points right now. To know what happened in between, you need a place where every real version is lined up. That is also how I noticed 2.1.279 doesn't exist.
Second, vkey turns each version into a tuple before comparing. Compared as strings, 2.1.99 is greater than 2.1.284. The first time I tripped on this, the gap came out negative and I stared at it for a while.
Third, the GitHub release-note fetch is wrapped in try and never stops the run. Unauthenticated GitHub API calls are capped at 60 per hour, and it's easy to hit that when other tools share the same address. Headlines are garnish; the version gap is the meal. Writing that priority into the code is what keeps the morning line from going missing.
Step 4: Run it and read the result
This is the unedited output from this morning.
stable 2.1.277 (2026-09-18)latest 2.1.284 (2026-09-28)gap 6 releases / 10 days (arithmetic says 7)installed 2.1.284: 0 releases behind latestnot yet on stable: 2.1.278 2026-09-19 Changed auto mode for Claude API and Enterprise users, and on Bedrock, Vertex, Foundry and gateways, 2.1.280 2026-09-22 Added Claude Opus 5.5 (`claude-opus-5-5`), now the default Opus model — 1M context, $4/$20 per Mtok 2.1.281 2026-09-23 Added Claude apps gateway support for newer Claude Desktop keys in `desktop` policy blocks, includin 2.1.282 2026-09-24 Added a `maxProseWidth` setting that caps the width of Claude's prose in wide terminals while tables 2.1.283 2026-09-25 Added `x-claude-code-prompt-id` to the gateway hint headers so LLM gateways can group the requests t 2.1.284 2026-09-28 Added Claude Sonnet 5.5 (`claude-sonnet-5-5`), now the default Sonnet model on the Anthropic API — 1
The installed line shows 0 because this machine follows latest. On a stable-channel machine the same line becomes installed 2.1.277: 6 releases behind latest. The list stays non-empty even when your own gap is zero, and that is deliberate: it reflects the state "I am current, but a colleague on stable is not."
Some of what the numbers showed me I only learned by counting, so I'll set it down honestly.
Subtraction lies. As mentioned above, 2.1.279 does not exist. I've known for a long time that version numbers skip, yet I had still been doing "how far behind" in my head as a subtraction. A tool that measures the gap has to count the sequence of releases that exist, not the numbers.
"About a week" is a pointer, not a window. I had pictured the stable channel as a window sliding along one week behind, every day. In reality, the pointer has not moved since 2.1.277 was published on September 18, even though 2.1.278 appeared the very next day. If the description about skipping releases with major regressions is accurate, then the pointer is something a person moves on purpose. I can't see the reasoning from the outside, but I did learn not to treat the day count as an assumption.
Release notes arrive after the binary. Comparing publish times for 2.1.284, the npm registry shows 17:11 UTC and the GitHub release note shows 18:02. That is a 51-minute gap. I suspect there have been mornings where I checked only the release notes and wrote down "nothing new." That is why the script confirms a version's existence and its headline in two different places.
One more thing came from re-reading the documentation. Switching from latest to stable records your current version as minimumVersion, so auto-update won't move you to an older build. I had braced for a downgrade when switching, and that note took a weight off my shoulders.
Make it one line a day
Once the tool exists, put it somewhere that runs without you remembering. I added one line to the morning checks on my operations machine.
# append one line at 08:05 every morning (script placed in ~/bin)5 8 * * * /usr/bin/python3 "$HOME/bin/claude_channel_gap.py" --quiet >> "$HOME/claude_channel.log" 2>&1
The accumulated lines become the material for a decision. In a week where the gap sits at 2 releases, staying on stable costs you nothing. On a morning like today — 6 releases, 10 days, two default-model changes among them — you can decide that same day whether to move ahead with claude update or to wait while naming the model explicitly with --model.
Looking at this morning's numbers, I came to think there is no once-and-for-all answer to "stable or latest." A channel isn't something you choose once; it's something you re-decide after measuring the gap. That is the one line I try not to cross, even in a busy week, as an indie developer keeping a few machines running.
Start by typing the two lines from Step 2 by hand and writing down today's stable and latest. That is where I started, too.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.