Claude model migration — retired IDs, new versions, and behaviour deltas
Claude 3 is retired. Claude 4.x is stable production. Claude Sonnet/Opus 5 and Fable 5 are the newest. Each generation has ID changes and subtle behaviour deltas — this page maps every migration path.
Quick fix (TL;DR)
claude-3-opus-20240229, claude-3-5-sonnet-20240620, etc.). Current stable: Claude 4.6 and 4.7 family plus Sonnet 5, Haiku 4.5, and Fable 5. Retired IDs return 404 or auto-substitute to a successor with different behaviour. Fix by (a) mapping every hardcoded model ID in your codebase, (b) testing behaviour on the target model against your prompt suite, and (c) using aliases (claude-sonnet-5) where you want auto-updates and pinned dates where you do not.Real error messages you'll see
These are the exact strings returned by the Claude API service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-3-opus-20240229 not found.'}}HTTP/1.1 200 OK anthropic-deprecation-notice: "claude-3-5-sonnet-20241022 is deprecated and will be retired on YYYY-MM-DD. Migrate to claude-sonnet-5."
# Before (claude-3-5-sonnet-20241022): tight, terse answers # After (claude-sonnet-5): more verbose, sometimes over-explains # Symptom: latency and token cost up 30% on the same workload.
Reference
Migration map — retired → current (2026)
| Retired ID | Recommended successor | Notes |
|---|---|---|
claude-3-opus-20240229 | claude-opus-4-7 | Massive capability jump, similar cost |
claude-3-sonnet-20240229 | claude-sonnet-5 | Faster + smarter |
claude-3-haiku-20240307 | claude-haiku-4-5-20251001 | Cheaper + smarter |
claude-3-5-sonnet-20240620 | claude-sonnet-5 | Similar tier, better |
claude-3-5-sonnet-20241022 | claude-sonnet-5 | Successor generation |
claude-3-5-haiku-20241022 | claude-haiku-4-5-20251001 | Successor generation |
claude-3-7-sonnet-20250219 | claude-sonnet-5 or claude-opus-4-7 | Depends on task |
Current model families (2026)
| Family | Best for | Context | Notable features |
|---|---|---|---|
| Claude Opus 4.6/4.7 | Complex reasoning, long horizon coding, agents | 200K | Extended thinking, best quality |
| Claude Sonnet 4.6/5 | General purpose, cost-sensitive production | 200K / 1M | Fast + smart, extended thinking |
| Claude Haiku 4.5 | High-volume classification, quick tasks | 200K | Very fast, cheap |
| Claude Fable 5 | Creative writing, story generation | 200K | Long-form narrative optimised |
Root causes, ranked by frequency
Based on developer reports across Claude API forums, GitHub issues, and Anthropic community during 2025–2026.
- 30%Retirement date reached with no migration. Team ignored deprecation warnings; day-zero 404 on production.
- 18%Auto-substitution changes behaviour. Anthropic (or Bedrock/Vertex) silently maps the old ID to a newer one; quality shifts, tests fail.
- 14%Hardcoded model IDs scattered across the codebase. Migration requires a code sweep; one straggler stays on the old ID.
- 10%Model behaviour differs on same prompt. Newer model produces more verbose or differently-formatted output; downstream parsers break.
- 8%Cost per request higher on the successor. New model priced higher per token even though it is smarter — total cost per task can be higher or lower.
- 7%API version mismatch. Successor model may require a newer
anthropic-versionheader. - 7%Provider platform lag. New Anthropic model available on the API but not yet on Bedrock/Vertex; team on Bedrock unable to migrate on the same schedule.
- 6%Fine-tunes retire with the base model. Custom Claude fine-tunes (where available) retire when their base retires.
Fixes — copy-paste solutions
Audit every hardcoded model ID in the codebase
Search the codebase for every model ID reference and consolidate to a single source of truth (constants module, env var). This is the necessary first step before behaviour testing.
# Find every hardcoded Claude model ID in the repo rg -n '"claude-[0-9a-z-]+"' \ --type py --type ts --type js --type go --type rb --type yaml --type json # Find bare model references (aliases without a version) rg -n 'model["\s]*[:=]\s*["\']?claude' \ --type py --type ts --type js # Common patterns in configs rg -n 'CLAUDE_MODEL\|ANTHROPIC_MODEL\|OPUS_MODEL\|SONNET_MODEL' \ --type py --type ts --type js --type yaml --type env # Once you have the list, consolidate to a single constants file # constants/models.py: # OPUS = "claude-opus-4-7" # or a pinned @date if you need stability # SONNET = "claude-sonnet-5" # HAIKU = "claude-haiku-4-5-20251001" # FABLE = "claude-fable-5" # Replace every hardcoded ID with the constant. Now migration is a one-file change.
@YYYYMMDD version and treat model upgrades as deliberate changes with sign-off. For agility, use unversioned aliases and let Anthropic move you.Test the migration on a prompt suite before cutover
Run your 20-100 most representative prompts through both the current and target models. Diff the outputs. Measure output length, latency, and any downstream parsing metrics.
"""Run a prompt suite against old + new model and diff results.""" import json import time import anthropic from pathlib import Path client = anthropic.Anthropic() OLD_MODEL = "claude-3-5-sonnet-20241022" # currently in production NEW_MODEL = "claude-sonnet-5" # target def run(model: str, prompt: dict): t0 = time.time() r = client.messages.create( model=model, max_tokens=prompt.get("max_tokens", 1000), system=prompt.get("system"), messages=prompt["messages"], ) elapsed = time.time() - t0 return { "model": model, "text": r.content[0].text if r.content else "", "output_tokens": r.usage.output_tokens, "input_tokens": r.usage.input_tokens, "elapsed_s": round(elapsed, 2), "stop_reason": r.stop_reason, } # Load your prompt suite prompts = json.loads(Path("prompt_suite.json").read_text()) results = [] for p in prompts: old = run(OLD_MODEL, p) new = run(NEW_MODEL, p) results.append({ "name": p["name"], "old": old, "new": new, "diff": { "output_tokens_delta": new["output_tokens"] - old["output_tokens"], "elapsed_delta_s": round(new["elapsed_s"] - old["elapsed_s"], 2), "same_stop_reason": old["stop_reason"] == new["stop_reason"], }, }) # Summarise total_old = sum(r["old"]["output_tokens"] for r in results) total_new = sum(r["new"]["output_tokens"] for r in results) print(f"Output tokens: old={total_old}, new={total_new}, delta={total_new-total_old:+d}") print(f"Avg latency: old={sum(r['old']['elapsed_s'] for r in results)/len(results):.2f}s, " f"new={sum(r['new']['elapsed_s'] for r in results)/len(results):.2f}s") # Manually review the diff on outputs where old vs new differ substantially
Decide: pinned versions or aliases?
Pinned versions (claude-sonnet-5-20250601) mean the model behaviour never changes under you — but you must migrate deliberately. Aliases (claude-sonnet-5) mean Anthropic can update the underlying model — smoother but less predictable.
"""Strategy: pin versions in prod, use aliases in dev/staging.""" import os # Environment-aware model selection ENVIRONMENT = os.getenv("APP_ENV", "development") MODEL_CONFIG = { "production": { # Pinned — behaviour stable, migration is a deliberate PR "opus": "claude-opus-4-7-20250514", "sonnet": "claude-sonnet-5-20251015", "haiku": "claude-haiku-4-5-20251001", }, "staging": { # Alias — validates that a future rollout of the new version works "opus": "claude-opus-4-7", "sonnet": "claude-sonnet-5", "haiku": "claude-haiku-4-5", }, "development": { # Alias — fastest to try new features "opus": "claude-opus-4-7", "sonnet": "claude-sonnet-5", "haiku": "claude-haiku-4-5", }, } def model(tier: str) -> str: return MODEL_CONFIG[ENVIRONMENT][tier] # Usage response = client.messages.create( model=model("sonnet"), max_tokens=500, messages=[...], ) # Migration path: # 1) Run test suite against latest alias in staging weekly # 2) When alias behaviour changes, decide: adopt or hold # 3) On adopt, bump the pinned version in production and deploy
Prevention checklist
Ship these seven safeguards once and this error stops appearing in your logs.
- Consolidate every Claude model ID to a constants module — no hardcoded IDs in application code.
- Subscribe to Anthropic's deprecation announcements (blog + email) and add retirement dates to your calendar.
- Run a monthly migration-test script even when no deprecation is announced — catches upstream behaviour drift.
- For regulated / high-stability workloads, pin
@YYYYMMDDversions and treat updates as deliberate changes. - For dev/staging environments, use aliases so future updates surface early.
- Maintain a "supported models" registry per platform (Anthropic API, Bedrock, Vertex) — availability lags differ.
- Include model migration in your quarterly platform-review meeting; deprecation surprises are avoidable.
Frequently asked questions
Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.