Sonnet 4.5 の退役日を公式の表で確かめたあと、私が最初にしたのは、リポジトリで claude-sonnet-4-5 を grep することでした。
出てきたのは、タグと URL のスラッグと、過去の記事本文でした。API を呼んでいる行は、1 本もありません。それでも安心はできませんでした。個人開発でサイトとアプリを動かしていると、「自分が書いた覚えのない呼び出し」がいちばん怖いのだと、あらためて感じています。コードに書かれていない呼び出し——環境変数、別マシンのスクリプト、ノーコードの連携、もう動かしたことを忘れていた古いキー——は、grep の網には掛からないからです。
退役日は 2026 年 11 月 30 日で、今日は 10 月 18 日。残りは 43 日です。急がなくてもまだ間に合う日数ですが、「どこで使っているか」を文字列検索で探すのをやめ、使われた事実の側から数えることにしました。その手順をお伝えします。
公式が決めている日付と、置き換え先
公式の model deprecations ページ(2026 年 10 月 18 日に確認)で、期限が付いているモデルは次のとおりです。
| モデル ID | 状態 | 退役(見込み) | 推奨される置き換え先 |
|---|---|---|---|
claude-sonnet-4-5-20250929 | Deprecated(9/30 告知) | 2026-11-30 | claude-sonnet-5-5 |
claude-opus-4-5-20251101 | Active | 11/24 より前にはならない | 未告知 |
claude-haiku-4-5-20251001 | Active | 10/15 より前にはならない | 未告知 |
下の 2 行は、まだ退役の告知ではありません。公式の表が「Tentative retirement date: Not sooner than」としている、最短の日付です。ただ、Sonnet 4.5 も告知から退役まで 61 日でした。公式は公開済みモデルに少なくとも 60 日の予告を約束していますので、この 2 行は「いつ告知が来てもおかしくない行」として棚卸しの対象に入れておくのが私の線引きです。
もう一点、日付は Anthropic が運用するプラットフォーム(Claude API、AWS 上の Claude Platform、Microsoft Foundry)のものです。Amazon Bedrock と Google Cloud は、それぞれの退役スケジュールを持っています。Bedrock 経由で呼んでいる方は、Bedrock 側の表をご覧ください。
grep は「書かれている」ことしか教えてくれません
棚卸しの道具を 2 つに分けました。
- コード側: 設定ファイルとスクリプトに残ったモデル ID の数(書かれているもの)
- 利用実績側: Console の Usage ページから書き出した CSV の、キー別・モデル別の量(実際に使われたもの)
公式の手順は単純です。Console の Usage ページを開き、Export を押し、ダウンロードした CSV でモデル別・API キー別の内訳を確認します。コードに書かれているのに 1 トークンも流れていない行は後回しにでき、コードに見当たらないのに流れているキーは、真っ先に持ち主を探すべき対象になります。
最初に私がつまずいたのは、コード側の数え方でした。リポジトリ全体に対して claude- で始まる文字列を拾うと、記事本文のモデル名まで入って、417 件も出たのです。数字が大きいほど「まだ大量に残っている」と錯覚します。いま思えば、数字の大きさに安心も不安も預けていたのは私の側でした。拾った件数ではなく、拾った種類を先に疑う——この線引きを、スクリプトの最初の仕様にしました。
2 つの数え方を 1 本にしたスクリプト
次のスクリプトは、ディレクトリを走査してモデル ID を数え、期限付きの ID には日付と置き換え先を添えます。--code を付けると、コード・設定の拡張子だけを見ます。--csv に Usage の CSV を渡すと、model を含む列でグルーピングして、数値の列をすべて合計します。
列名は固定していません。Console の CSV の見出しは、私の手元では確認できていないからです。公式が示しているのは「API キー別・モデル別の内訳」という点までで、列名は書かれていません。そのため、見出しに model を含む列を探す作りにしました。見つからなければ、見出しの一覧を出して止まります。
#!/usr/bin/env python3
"""model_inventory.py — コード内のモデル ID と、Console の Usage CSV を同じ表で突き合わせる。
使い方: python3 model_inventory.py <ディレクトリ> [--code] [--csv usage.csv]
--code 設定・コードの拡張子だけを見る(記事本文やタグの文字列を拾わない)
"""
import csv, re, sys, pathlib, collections
# 公式 model-deprecations の「Model status」表(2026-10-18 取得)から、期限のあるものだけ
RETIRE = {
"claude-sonnet-4-5-20250929": ("2026-11-30", "claude-sonnet-5-5"),
"claude-opus-4-5-20251101": ("2026-11-24以降(最短)", "claude-opus-5-5"),
"claude-haiku-4-5-20251001": ("2026-10-15以降(最短)", "claude-haiku-5-5"),
}
SKIP_DIRS = {".git", "node_modules", ".next", "dist", "build", "__pycache__", "generated"}
CODE_EXT = {".py", ".js", ".mjs", ".ts", ".tsx", ".sh", ".yaml", ".yml", ".toml", ".env", ".cfg", ".ini"}
SKIP_EXT = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".pdf", ".zip", ".lock", ".woff", ".woff2"}
# 数字で終わる ID だけを拾う。slug(claude-opus-4-6-release など)を巻き込まない
ID_RE = re.compile(
r"claude-(?:(?:sonnet|opus|haiku|fable|mythos)-\d+(?:-\d+)?(?:-\d{8})?"
r"|3-(?:5-haiku|haiku|7-sonnet)-\d{8})(?![A-Za-z0-9]|-[A-Za-z0-9])"
)
def scan(root, code_only=False):
hits = collections.defaultdict(list)
for p in pathlib.Path(root).rglob("*"):
if not p.is_file() or p.suffix.lower() in SKIP_EXT:
continue
if any(part in SKIP_DIRS for part in p.parts):
continue
if code_only and p.suffix.lower() not in CODE_EXT and p.name != ".env":
continue
try:
text = p.read_text(encoding="utf-8")
except (UnicodeDecodeError, OSError):
continue
for n, line in enumerate(text.splitlines(), 1):
for m in ID_RE.findall(line):
hits[m].append(f"{p}:{n}")
return hits
def read_usage(path):
"""model を含む見出しの列でグループ化し、数値列を合計する。列名は固定しない。"""
with open(path, newline="", encoding="utf-8-sig") as f:
rows = list(csv.DictReader(f))
if not rows:
return {}
cols = rows[0].keys()
mcol = next((c for c in cols if "model" in c.lower()), None)
if mcol is None:
sys.exit("model を含む列が見つかりません: " + ", ".join(cols))
numeric = [c for c in cols if c != mcol and all(_num(r[c]) is not None for r in rows if r[c] != "")]
out = collections.defaultdict(lambda: collections.Counter())
for r in rows:
for c in numeric:
v = _num(r[c])
if v is not None:
out[r[mcol]][c] += v
return out
def _num(s):
try:
return float(str(s).replace(",", ""))
except ValueError:
return None
def main():
args = sys.argv[1:]
root = args[0] if args else "."
csv_path = args[args.index("--csv") + 1] if "--csv" in args else None
hits = scan(root, code_only="--code" in args)
print(f"== コード内のモデル ID({root})")
for mid in sorted(hits, key=lambda k: (k not in RETIRE, k)):
note = ""
if mid in RETIRE:
d, to = RETIRE[mid]
note = f" ← 期限 {d} / 置き換え先 {to}"
print(f"{len(hits[mid]):4d} {mid}{note}")
if mid in RETIRE:
for loc in hits[mid][:5]:
print(f" {loc}")
if csv_path:
print(f"\n== Usage CSV({csv_path})")
for model, cnt in read_usage(csv_path).items():
flag = " ← 期限あり" if model in RETIRE else ""
print(model + flag)
for c, v in cnt.items():
print(f" {c}: {v:,.0f}")
if __name__ == "__main__":
main()モデル ID の正規表現には、癖があります。claude-sonnet-4-6-vs-... のような URL スラッグを巻き込まないよう、「ID の直後に英数字かハイフン+英数字が続くものは拾わない」と先読みで制限しました。最初の版ではここが緩く、claude-sonnet-4 が単独で 4 件も出てしまい、バックトラックで先頭だけが一致していたのが原因でした。
動かした結果
まず、このサイトのリポジトリ(Next.js)でコードと設定だけを走査しました。
python3 model_inventory.py ~/repos/claudelab.net --code結果は、見出しの行が 1 本出ただけで、中身は空でした。Claude API を呼ぶコードは、このリポジトリには 1 行もありません。--code なしで走らせると 400 件を超える一致が出ますが、すべて記事本文・タグ・URL スラッグです。数が多いことと、移行が必要なことは別の話でした。
次に、期待どおり動くかを確かめるため、動作確認用の小さな CSV を手で作りました(実際の Console の書き出しではなく、列名も私が仮に決めたものです)。
date,api_key,model,input_tokens,output_tokens
2026-10-01,key_wallpaper_batch,claude-sonnet-4-5-20250929,1200000,210000
2026-10-01,key_wallpaper_batch,claude-sonnet-5-5,300000,52000
2026-10-01,key_translate,claude-haiku-4-5-20251001,880000,140000
2026-10-02,key_translate,claude-haiku-4-5-20251001,910000,150000
2026-10-02,key_wallpaper_batch,claude-sonnet-4-5-20250929,1100000,190000python3 model_inventory.py ~/repos/claudelab.net --code --csv usage_sample.csv== Usage CSV(usage_sample.csv)
claude-sonnet-4-5-20250929 ← 期限あり
input_tokens: 2,300,000
output_tokens: 400,000
claude-sonnet-5-5
input_tokens: 300,000
output_tokens: 52,000
claude-haiku-4-5-20251001 ← 期限あり
input_tokens: 1,790,000
output_tokens: 290,000このサンプルの数字は架空です。見ていただきたいのは、期限付きのモデルに旗が立ち、移行が済んだ分(claude-sonnet-5-5)と未了の分が同じ画面で並ぶという形です。本物の CSV で列名が違った場合は、read_usage の中の 1 行("model" in c.lower())だけを直せば足ります。
見つかった行のあと、手を付ける順番
文字列の置き換えだけで終わらせず、次の順に進めるつもりです。
- Usage の CSV で、
claude-sonnet-4-5-20250929を実際に流しているキーを特定します(持ち主と用途を書き出します) - そのキーの呼び出し元を探します。コード側のスキャンで見つからなければ、環境変数・CI の変数・ノーコード連携の設定画面を順に開きます
- モデル ID を置き換える前に、移行ガイドの引数の差分を確認します(budget_tokens・prefill・
tool_choiceなど。400 になる項目は「Sonnet 4.5 の退役前に、コードに残った「400 になる引数」を一本のスクリプトで拾いました」で一覧にしています) - 置き換えた 1 週間後にもう一度 CSV を書き出し、旧 ID の行が増えていないことを確かめます
4 番目を入れたのは、置き換えが「済んだつもり」で終わりやすいからです。古い設定のコピーが別のマシンに残っていれば、旧 ID はまた流れ始めます。書き出しをもう一度見るだけで防げる種類の取りこぼしですので、手間を惜しまないことにしました。
次のアクション
今週のうちに、Console の Usage から CSV を 1 回だけ書き出してみてください。モデルの列でソートして、claude-sonnet-4-5-20250929 の行があるかどうかを見るだけで十分です。ある場合は、API キーの名前から、持ち主と用途を紙に書き出すところまでを今日の仕事にします。
探すのは文字列ではなく、流れたトークンの持ち主です。 残り 43 日のうち、最初の 1 日をそこに使うことにしました。