◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 36 days left. If your app calls it, check the migration target●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 36 days left. If your app calls it, check the migration target●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/AI Models
◉ AI Models/2026-10-25Intermediate

Is Your Rork App Still Calling Sonnet 4.5? Finding Out Before November 30

Claude Sonnet 4.5 is scheduled to retire from the API on November 30, 2026. A small script to find leftover model IDs in your app, and the order to fix them in depending on where they live.

Claude API12Sonnet 4.5model retirementExpo227Supabase40Rork599

Picture opening an old chat-enabled app of yours and searching the source for "claude". The model name turns up in more places than you expected: one environment variable, one screen component, one server-side function. Which of them is actually live is not obvious at a glance.

According to Anthropic's deprecations page, Claude Sonnet 4.5 was announced as deprecated on September 30, 2026, with retirement on November 30, 2026 (marked tentative in the status table). The suggested replacement is claude-sonnet-5-5. That is 61 days of notice, in line with the stated policy of at least 60 days for publicly released models.

Here is a script that finds the model IDs left in your project, and how I would order the fixes depending on where each one turns up.

First, what is and is not in scope

Two things are easy to mix up.

The only row I could confirm on the deprecations page was the dated ID claude-sonnet-4-5-20250929. The undated alias claude-sonnet-4-5 is not listed, so the script treats it as the same family and flags it. claude-haiku-4-5-20251001 is Active with no retirement date, only a "not sooner than October 15" estimate, so it goes in a watch column rather than the action column.

The scan script

No dependencies, just Node.js. It collects every string starting with claude-, and skips node_modules and generated ios / android folders.

// scan-claude-models.mjs — find every Claude model ID in a project
// usage: node scan-claude-models.mjs ./my-app
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative, 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", ".sql", ".md"]);
 
// Update these from Anthropic's model deprecations page (dates are as checked on 2026-10-25)
const RETIRING = {
  "claude-sonnet-4-5": "retires 2026-11-30 (suggested replacement: claude-sonnet-5-5)",
};
const WATCH = {
  "claude-haiku-4-5": "no retirement announced (only a 'not sooner than 2026-10-15' estimate)",
};
 
const idPattern = /claude-[a-z0-9]+(?:-[a-z0-9]+)*/g;
const hits = [];
 
function walk(dir) {
  for (const name of readdirSync(dir)) {
    if (SKIP_DIRS.has(name)) continue;
    const path = join(dir, name);
    const st = statSync(path);
    if (st.isDirectory()) { walk(path); continue; }
    const isEnv = name === ".env" || name.startsWith(".env.");
    if (!isEnv && !EXTS.has(extname(name))) continue;
    if (st.size > 1_000_000) continue;
    readFileSync(path, "utf8").split("\n").forEach((line, i) => {
      for (const m of line.matchAll(idPattern)) {
        hits.push({ id: m[0], file: relative(root, path), line: i + 1 });
      }
    });
  }
}
walk(root);
 
// Treat dated IDs (claude-sonnet-4-5-20250929) and the undated alias as the same family
const base = (id) => id.replace(/-\d{8}$/, "");
let danger = 0;
for (const h of hits) {
  const b = base(h.id);
  const label = RETIRING[b] ? "ACTION" : WATCH[b] ? "WATCH" : "OK";
  const note = RETIRING[b] ?? WATCH[b] ?? "";
  if (RETIRING[b]) danger++;
  console.log(`${label} | ${h.id} | ${h.file}:${h.line} | ${note}`);
}
console.log(`\n${hits.length} found / ${danger} need action`);
process.exit(danger > 0 ? 1 : 0);

Run it like this:

node scan-claude-models.mjs ./my-app

It exits with code 1 when anything needs action, so you can later wire it into a pre-build check. Edit RETIRING and WATCH from the deprecations page whenever you run it; the status table changes over time.

This is the output on a dummy project with model IDs placed in four spots (plus one inside node_modules, to confirm it is skipped):

ACTION | claude-sonnet-4-5-20250929 | .env:1 | retires 2026-11-30 (suggested replacement: claude-sonnet-5-5)
ACTION | claude-sonnet-4-5 | src/chat.ts:1 | retires 2026-11-30 (suggested replacement: claude-sonnet-5-5)
WATCH | claude-haiku-4-5-20251001 | src/chat.ts:2 | no retirement announced (only a 'not sooner than 2026-10-15' estimate)
ACTION | claude-sonnet-4-5-20250929 | supabase/functions/chat/index.ts:1 | retires 2026-11-30 (suggested replacement: claude-sonnet-5-5)

4 found / 3 need action

Where it was found decides the order

Every ACTION line looks the same, but the time each takes to fix is very different.

Where it was foundWhat a fix requiresStart
Server-side function (Supabase Edge Functions, etc.)Change the ID and redeploy. No store reviewBy the second week of November
App code, or an EXPO_PUBLIC_ environment variableThe value is baked into the build, so you need a new release and users who updateThis week

Expo inlines EXPO_PUBLIC_ variables into the app at build time. Editing .env on your machine does nothing for an app already in users' hands. Add store review time and the days it takes people to update, and the runway to November 30 is shorter than it looks.

As an indie developer, the rule I draw from this: keep the model ID on the server, not in the app. It follows the same logic as keeping the API key out of the app, and the next time a model is swapped out, a redeploy is all it takes. If the scan shows IDs baked into the app, moving them server-side during this migration saves you the same work next time.

Compare outputs before swapping

The suggested replacement is claude-sonnet-5-5, but I would not treat this as a find-and-replace. Response length and tone can shift, and that can affect layouts or whatever processes the output downstream.

When I swap a model in my own apps, my order is:

  1. Pick three real inputs from your app and save the current model's outputs.
  2. Run the same inputs with the new ID and compare length, format (if a feature returns JSON, does it still parse?), and tone.
  3. Check pricing and rate limits on the official pricing page before switching. I am deliberately not quoting numbers here, since they may have changed.
  4. If it all holds up, change the server-side ID and redeploy.

In the same spirit of checking before you upgrade, I also wrote a pre-upgrade scan for Expo: SDK 58 で削除された React Native の API を、アップグレード前に洗い出すスクリプト (Japanese title).

One thing to do today

Run the script once and count the ACTION lines. Zero means nothing to do this time. Even one means finding out whether it sits on the server or in the app, and that answer settles the order of everything else.

Dates on the deprecations page can change, so look at the Sonnet 4.5 row once more right before you make the switch.

Share

Thank You for Reading

Rork 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.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◉ AI Models2026-10-20
Before You Type "Connect Supabase": The Two Values to Copy, and the One You Must Not
Rork asks for just two things before you type Connect Supabase in chat: a Publishable key and your project link. Here is where to copy them from, how to avoid pasting the secret key that sits on the same screen, and a small script that checks the values locally first.
◉ AI Models2026-10-14
Take the Prompt Out of the App: An Edge Function Gateway With the Guard on the Output Side
A system prompt shipped inside your app is readable from the day it ships. This walkthrough moves the prompt into a Supabase Edge Function, lets the client name only a task, and puts the guard on an output allowlist, with tested code.
◉ AI Models2026-09-13
Is That API Key Sitting Inside Your App? Rork Environment Variables vs. Supabase Edge Functions
Rork gives you two places to put an API key: its own environment variables, or a secret inside a Supabase Edge Function. Here is how I decide between them based on what I can revoke and who gets the bill, plus three checks to run after the key is in place.