Skip to content

Preserve Opus fallback on older Claude Code#4480

Open
mihneaptu wants to merge 1 commit into
pingdotgg:mainfrom
mihneaptu:agent/fix-opus5-compat
Open

Preserve Opus fallback on older Claude Code#4480
mihneaptu wants to merge 1 commit into
pingdotgg:mainfrom
mihneaptu:agent/fix-opus5-compat

Conversation

@mihneaptu

@mihneaptu mihneaptu commented Jul 24, 2026

Copy link
Copy Markdown

Summary

  • preserve the bare opus selection when Claude Code's live catalog does not include Opus 5
  • choose the newest available built-in Opus independently of user picker ordering
  • restrict the fallback to the canonical built-in Opus slugs, so arbitrary custom names such as claude-opus-custom are not selected, while an exact custom model named opus is still preserved by the direct-slug match
  • keep explicit Opus 5 aliases unavailable when Opus 5 is not in the catalog

Root cause

#4472 updated the bare opus alias to claude-opus-5. Claude Code versions below 2.1.219 omit Opus 5 from their live catalog, so resolveSelectableModel() returned null and T3 fell back to the provider default, currently Sonnet, instead of the newest available Opus. This resolves the unresolved review finding in #4472 (comment).

Validation

  • vp test run packages/shared/src/model.test.ts apps/web/src/modelSelection.test.ts — 16 tests passed
  • pnpm --filter @t3tools/shared typecheck — passed
  • pnpm --filter @t3tools/web typecheck — passed
  • targeted formatting and lint — passed
  • git diff --check — passed
  • Claude adapter suite — 62 tests passed

An isolated desktop build also completed, ran migrations, started its backend, and opened a responsive T3 Code (Dev) window. A successful live Claude response was not available locally because the account hit its session limit and 1M-context usage requires credits; those external limits do not affect the resolver coverage in this PR.

Note

Preserve Opus model fallback in resolveSelectableModel for older Claude Code

When the provider is claudeAgent and the requested value is 'opus', resolveSelectableModel now iterates a fixed priority list (CLAUDE_OPUS_FALLBACK_MODELS) and returns the newest available Claude Opus model slug instead of returning null. This ensures older Claude Code clients that send the bare 'opus' alias still resolve to a valid model when the normalized target slug is not present in the available options.

Macroscope summarized 29a898e.


Note

Low Risk
Scoped change to Claude model resolution in shared model selection logic, with unit tests; no auth or data-path changes.

Overview
Fixes bare opus on Claude so it no longer fails resolution when the live catalog does not include the newest Opus (e.g. older Claude Code without Opus 5), which previously caused T3 to fall back to the provider default (often Sonnet).

resolveSelectableModel now, for claudeAgent and input exactly opus, picks the newest available slug from a fixed built-in list (claude-opus-5 down through 4-5). That list is independent of picker order and does not treat custom claude-opus-* slugs as fallbacks; explicit aliases like opus-5 still resolve only if that slug is in the catalog.

Tests cover current catalogs, older catalogs, unavailable explicit Opus 5, and custom-only catalogs.

Reviewed by Cursor Bugbot for commit 29a898e. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe22d3e6-6d8b-4d74-907e-00d9fcafb498

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Jul 24, 2026
@mihneaptu
mihneaptu marked this pull request as ready for review July 24, 2026 22:02
@mihneaptu

Copy link
Copy Markdown
Author

This is a focused follow-up to #4472, not another Opus 5 integration.

It resolves the unresolved review finding at #4472 (comment): after opus was repointed to claude-opus-5, Claude Code versions below 2.1.219 omit Opus 5 from the live catalog, so resolveSelectableModel() returned null and selection fell through to the provider default (Sonnet) instead of the newest available Opus.

The fix keeps the bare opus alias inside the Opus family by walking a fixed built-in priority list, so it is independent of the user's modelOrder preferences and ignores custom claude-opus-* slugs. Explicit opus-5 still resolves to nothing when Opus 5 is absent, and a custom model named exactly opus is still preserved by the existing direct-match path.

Scope is two files in packages/shared. No SDK bump and no changes to the version gate.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 29a898e. Configure here.

// cannot change which fallback is selected.
if (provider === ProviderDriverKind.make("claudeAgent") && trimmed === "opus") {
const availableSlugs = new Set(options.map((option) => option.slug));
return CLAUDE_OPUS_FALLBACK_MODELS.find((slug) => availableSlugs.has(slug)) ?? null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fallback picks custom Opus slugs

Medium Severity

For bare opus, the new fallback treats any matching option slug in CLAUDE_OPUS_FALLBACK_MODELS as eligible, including custom models added via getAppModelOptionsForInstance. That can resolve opus to a user-defined claude-opus-* custom slug when no built-in Opus is in the catalog, instead of ignoring those slugs as described for this change.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 29a898e. Configure here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the mechanism is real, but the trigger is narrower than the description suggests, so I have left the behavior as-is for now.

The fallback compares slug strings, so it cannot distinguish a built-in entry from a custom one. I confirmed with a throwaway test that a catalog containing no built-in Opus plus a custom model whose slug is literally claude-opus-4-8 does resolve opus to that custom slug.

Two things bound it:

  1. normalizeCustomModelSlugs() in apps/web/src/modelSelection.ts already drops any custom slug that collides with a built-in model, so this requires a catalog where the colliding name is not currently built-in (an older Claude Code, on a custom instance, with a deliberately colliding name).
  2. Only the five canonical slugs in CLAUDE_OPUS_FALLBACK_MODELS are eligible. Arbitrary custom names such as claude-opus-custom are ignored, which is the case covered by the new test.

In that narrow scenario the result is a model the user explicitly named after a real Opus release, which seems closer to intent than falling through to the Sonnet default. An exact custom model named opus is also still preserved by the direct-slug match before any alias handling.

Fair hit on the PR wording though: "ignore custom claude-opus-* slugs" was looser than the code. I have corrected the description.

If maintainers would rather have this enforced strictly, the fix is to thread the existing isCustom flag into SelectableModelOption and skip custom entries in the fallback. That touches every call site that builds model options, so I kept it out of this two-file change — happy to add it here or as a follow-up if you prefer.

@macroscopeapp

macroscopeapp Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

An unresolved review comment identifies a potential logic bug where the fallback may incorrectly select custom Opus model slugs instead of only built-in ones, contradicting the stated intent. This model selection behavior change warrants human verification.

You can customize Macroscope's approvability policy. Learn more.

@mihneaptu

Copy link
Copy Markdown
Author

Live verification update: the earlier caveat in this description is now resolved. Claude Code 2.1.219 on Windows, real Opus 5 responses confirmed.

  • claude -p --model claude-opus-5 returns a real answer
  • all five efforts respond: low, medium, high, xhigh, max
  • claude-opus-5[1m] responds, confirming the 1M context form is accepted
  • the bare opus alias resolves and responds

The focused resolver coverage still passes: packages/shared/src/model.test.ts plus apps/web/src/modelSelection.test.ts, 16 tests.

One unrelated finding worth flagging separately from this PR. The five tests in apps/server/src/textGeneration/ClaudeTextGeneration.test.ts fail on Windows on unmodified main at 41a430a, independently of this change. They are not hermetic on Windows: the fixture prepends a fake claude script to PATH using a POSIX : separator and writes it extension-less, so the Windows resolver skips it (no PATHEXT match) and the real installed CLI is invoked instead. Failures therefore appear as live model prose replacing expected fixtures, or as live 429 / Not logged in errors. Happy to open a separate issue or PR for that if useful; it is out of scope here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant