Deadlines tend to slip past me the same way: I read the notice, think "I'll look at that later," and later never arrives. The Sonnet 4.5 retirement was heading that way for me.
When I opened Anthropic's model deprecations page on October 27, 2026, it listed claude-sonnet-4-5-20250929 as deprecated since September 30, with a retirement date of November 30, 2026 (tentative). The recommended replacement is claude-sonnet-5-5. After the retirement date, requests to the old model fail.
If you added AI features to an app, or called the Claude API yourself from a project you exported from Rork, it is worth checking that date against your own code. Below is the path I followed and actually ran: find the IDs, replace them, check again.
What to know first
You are affected directly only if you wrote the model ID yourself. If you simply pick a model from Rork's menu, the menu already points at Sonnet 5.5, so there is little for you to do.
But an exported project can carry claude-sonnet-4-5-20250929 in an API call, a server function, or a .env file, and any of those stops working after the date. IDs scatter easily. You fix one file, feel relieved, and the same string is still sitting in another.
Step 1: Drop in the audit script
Searching by eye misses things, so I use a small script that walks the project folder. It runs on Node.js with no dependencies. Save it as audit-models.mjs.
// audit-models.mjs — プロジェクト内の Claude モデル ID を洗い出し、廃止予定のものに印を付ける
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, extname } from "node:path";
const ROOT = process.argv[2] ?? ".";
const SKIP_DIRS = new Set(["node_modules", ".git", ".expo", "ios", "android", "dist", "build"]);
const EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".json", ".env", ".toml", ".yml", ".yaml", ".md"]);
// 公式の廃止ページ(platform.claude.com/docs/en/resources/model-deprecations)で確認した値。実行前に見直すこと。
const RETIRING = {
"claude-sonnet-4-5-20250929": { on: "2026-11-30", to: "claude-sonnet-5-5" },
};
// 別名(エイリアス)。固定のスナップショットではなく、先で指す先が変わりうるもの
const ALIAS = /^claude-sonnet-4-5$/;
// claude-<名前>-<数字>(-<数字>)(-<8桁日付>) の形を拾う
const MODEL_RE = /claude-(?:[a-z]+-)?(?:[a-z]+-)?\d+(?:-\d+)?(?:-\d{8})?/g;
const hits = [];
function walk(dir) {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
const st = statSync(p);
if (st.isDirectory()) { if (!SKIP_DIRS.has(name)) walk(p); continue; }
if (!EXTS.has(extname(name)) && !name.startsWith(".env")) continue;
readFileSync(p, "utf8").split("\n").forEach((line, i) => {
for (const m of line.matchAll(MODEL_RE)) hits.push({ file: p, line: i + 1, id: m[0] });
});
}
}
walk(ROOT);
let urgent = 0;
for (const h of hits) {
const r = RETIRING[h.id];
const tag = r ? `要置換 → ${r.to}(${r.on} 終了)` : ALIAS.test(h.id) ? "別名: 指す先を公式で確認" : "対象外";
if (r) urgent++;
console.log(`${h.file}:${h.line}\t${h.id}\t${tag}`);
}
console.log(`\n合計 ${hits.length} 件 / 要置換 ${urgent} 件`);
process.exit(urgent > 0 ? 1 : 0);Run it like this:
node audit-models.mjs ./my-appIf even one line needs replacing, the exit code is 1. That value becomes your pass/fail signal later.
Step 2: What it found in a test folder
Before pointing it at a real project, I tried it on a throwaway folder with four files: .env, src/ai.ts for the app side, supabase/functions/chat/index.ts for a server function, and src/notes.ts containing only a comment.
| Location | ID found | Verdict |
|---|---|---|
| .env, line 1 | claude-sonnet-4-5-20250929 | Replace |
| src/ai.ts, line 1 | claude-sonnet-4-5-20250929 | Replace |
| src/ai.ts, line 2 | claude-haiku-4-5-20251001 | Not affected |
| src/notes.ts, line 1 (in a comment) | claude-sonnet-4-5 | Alias: check what it points to |
| supabase/functions/chat/index.ts | claude-sonnet-4-5-20250929 | Replace |
Six hits in total, three needing replacement. What I did not expect was that the same ID lived in three separate layers: config, app, and server function. I built that folder myself, but as an indie developer I know how it happens in real apps: every time you add a feature, you copy the string along.
Step 3: Replace, then run it again
The replacement target is claude-sonnet-5-5, taken from the migration example on the official page. The command only touches the exact old ID, so other models stay as they are.
grep -rl "claude-sonnet-4-5-20250929" ./my-app --exclude-dir=node_modules --exclude-dir=.git \
| xargs sed -i 's/claude-sonnet-4-5-20250929/claude-sonnet-5-5/g'
node audit-models.mjs ./my-app
echo "exit=$?"In the test folder the second run printed "6 hits / 0 to replace" and the exit code dropped to 0. On macOS, sed -i needs an empty string after it (sed -i '' 's/…/…/g').
Pitfalls: replacing is not the end
The script only tells you that the string is gone. Whether your app still behaves well on the new model is a separate check. Response length, phrasing, and output format can shift. Anything that asks for JSON, or branches on the first words of a reply, is worth running by hand.
I also left date-less aliases such as claude-sonnet-4-5 out of the verdict. What an alias resolves to has to be confirmed in the official docs, and I could not state it with certainty, so the script just says it cannot decide.
Finally, the script reads only source and config files. It cannot see values typed into an admin console, an environment variable set directly in Supabase or Cloudflare, or another service's settings. After fixing the code, open those places yourself once.
For next time: keep the model ID in one place
So the next retirement notice does not catch you off guard, here is a shape worth adopting while you are already editing. I did not run this one in the test folder, so take it as a sketch of the idea.
// src/config/models.ts — the model ID lives here and nowhere else
export const CHAT_MODEL = process.env.CLAUDE_CHAT_MODEL ?? "claude-sonnet-5-5";Callers use `import { CHAT_MODEL } from "./config/models"`. With that in place, the next retirement means editing one line.
Next step
Run node audit-models.mjs once on your project and look only at the count. If it is zero, you are done for this deadline.
For my part, this exercise reminded me that the model ID belonged in a single constant from the start, and I plan to begin there the next time I add a feature. Retirement dates can move, so it is worth reopening the official deprecations page once more in early November.