Skip to content

feat(models): add Sonnet 5 and Fable 5 with fallback#443

Open
dimakis wants to merge 1 commit into
mainfrom
session/2026-07-05-4e60fafc33fa
Open

feat(models): add Sonnet 5 and Fable 5 with fallback#443
dimakis wants to merge 1 commit into
mainfrom
session/2026-07-05-4e60fafc33fa

Conversation

@dimakis

@dimakis dimakis commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add claude-sonnet-5 and claude-fable-5 to model picker (server, mobile, desktop)
  • Fable thinking config: always-on, omit param (explicit disabled returns 400)
  • Add FALLBACK_MODEL (claude-opus-4-6): if selected model fails with 404/access error, auto-retry with fallback and notify user
  • Add Sonnet 5 to Vertex model map
  • Tests for Fable and Sonnet 5 thinking resolution

Test plan

  • model-spec.test.ts — 13/13 passing (added Fable + Sonnet 5 cases)
  • TypeScript compiles clean (npx tsc --noEmit)
  • Manual: select Sonnet 5 from picker, verify session starts on Vertex
  • Manual: select Fable 5, verify fallback message when on Vertex (no direct API key)

🤖 Generated with Claude Code

Add claude-sonnet-5 and claude-fable-5 to AVAILABLE_MODELS, both
frontend dropdowns, and Vertex model map. Fable thinking is always-on
(omit param to avoid 400). If a selected model is unavailable (404,
org policy block), automatically retry with Opus 4.6 as fallback and
notify the user via system message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimakis

dimakis commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Centaur Review

Found 6 issue(s) (1 critical) (4 warning).

server/chat.ts

The model fallback logic has a likely bug in the finally block firing a spurious session-end event during fallback, fragile error-message matching that could misclassify non-model errors, and no test coverage for the new retry path.

  • 🔴 bugs (L1130): The finally block unconditionally fires _onSessionChange?.(clientId, 'end', session.sessionId). When the fallback path is taken (line 1118 return startChat(...)), the finally executes while the fallback session is already in-flight — emitting a spurious 'end' event for clientId. If _onSessionChange triggers orchestrator/task-board cleanup, this could kill or orphan the fallback session. The fallback branch should skip the finally cleanup (e.g., set a flag like didFallback = true and guard the finally body). [fixable]
  • 🟡 unsafe_assumptions (L1091): isModelError matches on generic substrings like 'not found' and '404'. Any SDK error whose message happens to contain these words (e.g., "Conversation not found", "File not found", a 404 from an unrelated HTTP call) will trigger the fallback path and silently retry with a different model. Consider matching on more specific patterns (e.g., 'model_not_found', API error codes/types) or checking the error's structured fields instead of free-text search. [fixable]
  • 🟡 missing_tests (L1105): The model fallback logic (30+ new lines of error handling, recursive startChat call, worktree cleanup on fallback) has no test coverage. The existing model-spec.test.ts only covers parseModelSpec and resolveThinking. The fallback path has subtle interactions (cleanup ordering, registry state, finally block) that would benefit from at least a unit test verifying: (1) fallback is triggered for model errors, (2) fallback is NOT triggered for non-model errors, (3) fallback is skipped when already on the fallback model. [fixable]
  • 🟡 bugs (L1099): The 'No conversation found' check (existing code, line 1099) no longer falls through to the cleanup at lines 1126-1128 correctly in all cases. Before this PR, the if/else had two branches; now it has three. The expired-session branch still correctly falls through to the post-chain cleanup. However, note that canFallback could be true even when options.resume is set and the error is "No conversation found" — the substring 'not found' matches both isModelError and the resume check. The resume check wins because it's first, but this is fragile; a future reorder could cause resume failures to trigger model fallback instead. [fixable]

packages/harness/src/providers/anthropic-vertex.ts

The model fallback logic has a likely bug in the finally block firing a spurious session-end event during fallback, fragile error-message matching that could misclassify non-model errors, and no test coverage for the new retry path.

  • 🟡 regressions (L19): claude-fable-5 is added to AVAILABLE_MODELS and both frontend dropdowns but is missing from VERTEX_MODEL_MAP. When CLAUDE_CODE_USE_VERTEX=1, selecting Fable 5 falls through to the raw model string which may not resolve on Vertex AI. The description says "direct API only" — if Fable is intentionally Vertex-incompatible, consider filtering it out of the model picker when Vertex is the active provider, or documenting the omission. [fixable]

frontend/src/pages/ChatView.tsx

The model fallback logic has a likely bug in the finally block firing a spurious session-end event during fallback, fragile error-message matching that could misclassify non-model errors, and no test coverage for the new retry path.

  • 🔵 style (L226): The model options are hardcoded in both ChatView.tsx and DesktopChatView.tsx, duplicating the AVAILABLE_MODELS array in server/chat.ts. The server already exposes config via GET /api/config. Consider either importing a shared list from @mitzo/protocol or rendering options from AVAILABLE_MODELS to avoid maintaining three copies that can drift. [fixable]

Reviewed at 1ff9cfa

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant