火曜の朝は、運用している機械の版を控える作業から始めております。Claude Code の版番号は毎朝一行だけ書き留めていて、その日に何が届いたかを後から辿れるようにしているのです。今朝の控えは latest が 2.1.284 でした。ところが同じ手で stable を見に行くと、答えは 2.1.277 のまま、9 月 18 日から動いていませんでした。
この十日のあいだに、既定の Opus は 5.5 へ替わり、昨日の版では既定の Sonnet も 5.5 へ替わっています。安定版を選んだ機械は、そのどちらもまだ受け取っていない——そう気づいたとき、私が頼りにしていた「安定版はおよそ一週間遅れ」という理解を、数えて確かめたことが一度もなかったのだと思い至りました。
数えるための道具は、標準ライブラリだけで 80 行ほどに収まりました。その道具を組む手順を、今朝の実測値と一緒に書き残します。ターミナルで python3 が動く方なら、コードを書き慣れていなくても最後まで辿れるはずです。
先に、今朝の数字をお伝えします
最初にお伝えしたいのは、安定版の遅れは「およそ一週間」という言葉ではなく、実在するリリースの数で見るべきだ ということです。今朝の実測は次のとおりでした。
チャンネル 指している版 公開日(UTC)
stable2.1.277 2026-09-18
latest2.1.284 2026-09-28
差 6 リリース 10 日
差の 6 という数字には注釈が要ります。284 から 277 を引けば 7 ですが、2.1.279 という版は npm のレジストリに存在していません。引き算は 7 と答え、実在する版を数えると 6 になる——この食い違いは、後で書くスクリプトの設計を決めた出来事でした。
安定版の機械に届いていない変更は、公開されているリリースノートの先頭項目だけを拾っても、次の 6 件になります。
版 公開日 先頭の変更
2.1.278 09-19 API 利用者と Enterprise、Bedrock / Vertex / Foundry / ゲートウェイでの auto モードの分類器が、既定でサーバー側に(分類器分の課金なし)
2.1.280 09-22 Claude Opus 5.5(claude-opus-5-5)が既定の Opus に。1M コンテキスト、$4/$20 per Mtok、キャッシュ読み $0.20
2.1.281 09-23 Claude apps gateway が Desktop の新しいポリシーキーに対応
2.1.282 09-24 maxProseWidth 設定の追加
2.1.283 09-25 ゲートウェイ向けヒントヘッダに x-claude-code-prompt-id を追加
2.1.284 09-28 Claude Sonnet 5.5(claude-sonnet-5-5)が既定の Sonnet に。1M コンテキスト、$2/$10 per Mtok、キャッシュ読み $0.20
既定モデルの交代が二つ、課金に関わる既定の変更が一つ含まれていました。安定版を選ぶこと自体は正しい判断だと私は思っています。ただ、その選択が「何を待たせているか」を毎朝一行で知っていれば、待つか進むかを自分で決められるのです。
Step 1: 自分の機械がどちらを追っているかを確かめます
道具を組む前に、いま自分の手元がどちらのチャンネルにいるかを確かめます。確かめる場所は三つです。
# いま動いている版
claude --version
# 追っているチャンネル(settings.json に無ければ既定の latest)
grep -H autoUpdatesChannel ~/.claude/settings.json 2> /dev/null || echo "autoUpdatesChannel は未設定(latest)"
# ネイティブインストールなら、手元に保持している版の一覧
ls ~/.local/share/claude/versions/ 2> /dev/null
対話中なら /config を開いて Auto-update channel の行を見るだけでも足ります。公式ドキュメントの説明では、latest が既定で「新機能を出た瞬間に受け取る」、stable が「およそ一週間経った版を使い、大きな回帰のある版は飛ばす」とされています。
一つだけ補足いたします。Homebrew で入れている方は、この設定ではなく cask の名前でチャンネルが決まります。claude-code が stable、claude-code@latest が latest です。npm で入れている方は npm update -g では動かないことがあると注記されているので、上げるときは npm install -g @anthropic-ai/claude-code@latest を使うことをお勧めします。
Step 2: 二つの一行エンドポイントを叩きます
チャンネルがいまどの版を指しているかは、公開されている配布サーバーに一行で問い合わせられます。
curl -s https://downloads.claude.ai/claude-code-releases/stable
# -> 2.1.277
curl -s https://downloads.claude.ai/claude-code-releases/latest
# -> 2.1.284
答えは版番号だけの平文です。私はこの二行を先に手で叩いて、返り値の形を目で確かめてからスクリプトに移りました。これは自分への線引きでもあります——返り値の形を見ていないエンドポイントは、スクリプトの中で初めて呼ばないようにしています 。形が分からないまま組んだ解析は、動いた日と壊れた日の見分けがつかないのです。
npm のレジストリも同じ答えを返します。dist-tags に stable と latest が並んでいて、今朝はそれぞれ 2.1.277 と 2.1.284 でした。こちらには各版の公開日時(time)も入っているので、日数の計算にはこちらを借ります。
Step 3: スクリプトを組みます
以下が全文です。外部ライブラリは使いません。GitHub のリリースノートは取れたら見出しを添え、取れなくても版の差だけは必ず出すようにしてあります。
#!/usr/bin/env python3
"""claude_channel_gap.py
安定版(stable)と最新版(latest)の差を「実在するリリース」で数え、
届いていない変更の見出しを一覧にします。標準ライブラリだけで動きます。
"""
import json
import re
import subprocess
import sys
import urllib.request
from datetime import datetime
CHANNEL_URL = "https://downloads.claude.ai/claude-code-releases/ {} " # stable / latest が1行で返る
NPM_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code" # 公開日時(time)を借りる
GH_URL = "https://api.github.com/repos/anthropics/claude-code/releases?per_page=30"
def fetch (url, timeout = 15 ):
# GitHub API は User-Agent が無いと 403 を返すことがあるので必ず付ける
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)。文字列比較だと "2.1.99" > "2.1.284" になってしまう
return tuple ( int (x) for x in v.split( "." ))
def installed_version ():
# claude が入っていない機械でも落ちないように None を返す
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 None
def 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" ]
# プレリリース(2.1.284-beta 等)を除き、実在する x.y.z だけを昇順に並べる
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 ] # 引き算の答え。実在数と食い違うことがある
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: # 60回/時の制限に当たっても、版の差だけは出す
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()
なぜこう書いたかを、三つだけ添えます。
一つ目は、版の一覧を配布サーバーではなく npm レジストリの versions から取っていることです。配布サーバーは「いまの指し先」しか教えてくれません。あいだに何があったかは、実在する版が全部並んでいる場所で数える必要がありました。2.1.279 が無いと気づけたのも、この一覧のおかげです。
二つ目は、vkey で版番号をタプルに変えてから比べていることです。文字列のまま比べると 2.1.99 が 2.1.284 より大きいことになります。ここが最初につまずいた落とし穴で、差が負の数になって首を傾げました。
三つ目は、GitHub のリリースノート取得を try で包み、失敗しても止めないことです。認証なしの GitHub API は一時間に 60 回までで、他の作業と重なると簡単に上限に触れます。その回避として、見出しが取れない朝は取れないまま先へ進む作りにしました。見出しは添え物、版の差が本体——この優先順位をコードの形にしておくと、朝の一行が止まらなくなります。
Step 4: 動かして確かめます
今朝そのまま実行した出力です。書き換えていません。
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 latest
not 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
installed の行は、この機械が latest を追っているので 0 と出ています。stable を追う機械で動かすと、ここが installed 2.1.277: 6 releases behind latest に変わります。差が 0 でも一覧が空でないのは、「自分は最新だが、安定版の同僚には届いていない」という状態をそのまま映しているからです。
毎朝の控えに使うなら --quiet を付けます。
stable=2.1.277 latest=2.1.284 gap=6rel/10d installed=2.1.284
直感と食い違った三つのこと
数えてみて初めて分かったことを、正直に書き残します。
引き算は嘘をつきます。 冒頭に書いたとおり、2.1.279 は存在しません。版番号が飛ぶことは以前から知っていたのに、「いくつ遅れているか」を頭の中で引き算していた自分に気づきました。差を数える道具は、番号ではなく実在するリリースの並びを数えなければなりません。
「一週間」は窓ではなく、指し先です。 安定版の説明にある「およそ一週間経った版」を、私は一週間分ずれた窓が毎日滑っていく姿で想像していました。実際には、2.1.277 が 09-18 に公開されてから今日まで、指し先は一度も動いていません。翌日の 09-19 にはもう 2.1.278 が出ているのに、です。大きな回帰のある版を飛ばすという説明が本当なら、指し先は「誰かが選んで動かすもの」なのでしょう。理由まで外から知ることはできませんが、少なくとも日数を仮定に置いてはいけないのだと分かりました。
リリースノートは、バイナリより後から届きます。 2.1.284 の公開日時を見比べると、npm レジストリが 17:11(UTC)、GitHub のリリースノートが 18:02 でした。51 分の差です。リリースノートだけを見て「今日は何も出ていない」と控えた朝が、私にはあったのだと思います。だからこのスクリプトは、版の存在と見出しを別々の場所で確かめています。
ドキュメントを読み直して分かったことも一つあります。latest から stable へ切り替えると、その時点の版が minimumVersion として記録され、自動更新が下の版へ戻すことはないとされています。切り替えれば古い版に落ちるのだろうと身構えていたので、これは肩の荷が下りる注記でした。
毎朝一行に落とします
道具ができたら、覚えていなくても動く場所に置きます。私は運用機の朝の点検に一行足しました。
# 毎朝 8:05 に一行だけ追記する(~/bin に置いた場合)
5 8 * * * /usr/bin/python3 " $HOME /bin/claude_channel_gap.py" --quiet >> " $HOME /claude_channel.log" 2>&1
積み重なった行は、そのまま判断の材料になります。差が 2 リリースで落ち着いている週なら、安定版のままで何も失っていません。今朝のように 6 リリース・10 日と出て、その中に既定モデルの交代が含まれているなら、claude update で進むか、あるいは --model で明示指定して待つかを、その日に決められるのです。
モデルの別名がどの世代を指すかは、私の理解では CLI 側の版が持っています。古い版のまま opus と書き続けると、既定が替わったあとも前の世代を呼び続けることになります。切り替わりを記録して歯止めを掛ける方法は、以前にモデル切り替えの記録と歯止めを、フック2本で作る で書きました。設定を書き換える前にまず版を確かめる、という順番については非推奨の通知が出た朝、設定を書き換える前にバージョンを確かめた話 に記しています。
差を測ってから、決め直します
安定版か最新版か、という問いに一度きりの答えはないのだと、今朝の数字を眺めて思いました。チャンネルは一度選ぶものではなく、差を測ってから決め直すものです。 この線引きだけは、忙しい週でも崩さないようにしております。
まずは Step 2 の二行を手で叩いて、今日の stable と latest を書き留めるところから始めていただければと思います。私もそこから始めました。