fix(agent): stop Multica picking a default model for the Claude runtime (MUL-5283)#5915
Open
forrestchang wants to merge 1 commit into
Open
fix(agent): stop Multica picking a default model for the Claude runtime (MUL-5283)#5915forrestchang wants to merge 1 commit into
forrestchang wants to merge 1 commit into
Conversation
…me (MUL-5283) Which model an unset Claude agent runs on is the CLI's call. Execution already honoured that — an empty agent.model sends "" and Claude Code resolves its own default — but `Default: true` on claude-sonnet-4-6 leaked into effort validation, where Multica substituted Sonnet for a model it cannot know. Concretely: a user whose claude CLI is configured to Opus, with the model field left empty and thinking_level `xhigh`, had `--effort xhigh` silently dropped by the daemon guard (warn-only) because Multica validated against its own Sonnet guess. The picker wouldn't offer `xhigh` there either. Claude Code takes its default from settings files / managed policy / ANTHROPIC_MODEL and 2.1.219 has no `config` subcommand, so there is no headless way to resolve it. Treat claude like opencode: for an empty model accept any level the advertised catalog supports and let the CLI arbitrate, matching the daemon guard's stated fail-open philosophy. This only widens — nothing that validates today starts failing, and tokens outside the catalog (e.g. Codex's `ultra`) still fail closed. - ValidateThinkingLevel: empty claude model takes the catalog-union path. - claudeStaticModels: no entry flagged Default; the picker already renders an unset model as "Default" meaning "whatever the CLI picks", and the thinking picker deliberately stopped badging a default row. - pickLevels (frontend) mirrors the backend so the UI never hides a level the daemon would accept. Codex behaviour is unchanged: its empty model still fails closed (MUL-4347). Co-authored-by: multica-agent <github@multica.ai>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows up on review feedback from #5910 (MUL-5282):
Tracked as MUL-5283. #5910 stays scoped to Opus 5 support; this PR only changes who owns the default model.
The problem
Execution was already correct. When an agent's
modelis empty the daemon passes""and Claude Code resolves its own default — Multica does not pin it.But
Default: truewas not a display-only flag.ValidateThinkingLevelresolves an empty model to whichever catalog entry carries that flag — the hardcodedclaude-sonnet-4-6— and validates the thinking level against its effort allow-list.Failure path:
claudeCLI is configured to Opus (settings.json / managed policy /ANTHROPIC_MODEL)xhighValidateThinkingLevel(ctx, "claude", path, "", "xhigh")xhigh→false--effort xhighThe task still runs on Opus, but at the CLI's own effort — a silent downgrade visible only in
daemon.log. The frontendpickModelEntrymirrored the same logic, so the picker wouldn't offerxhighto those users either.Why Multica can't know
Claude Code's default comes from the user's own settings files / managed policy /
ANTHROPIC_MODEL, and claude-code 2.1.219 has noconfigsubcommand (verified) — there is no headless way to ask which model it would resolve to. AnyDefaultbaked into our catalog is a guess.The codebase was already heading this way:
thinking-picker.tsxdocuments that the "default model" badge was removed because "flagging one option as 'default' was misleading." The badge went; the validation proxy behind it did not.The change
Follows the pattern opencode already uses in this file (
anyModelSupportsThinkingValue):ValidateThinkingLevel— an empty claude model takes the catalog-union path: accept any level an advertised model supports and let the CLI arbitrate. Matches the daemon guard's own stated rule: "if we can't tell, keep the level and let the CLI object."claudeStaticModels()— no entry flaggedDefaultpickLevels(waspickModelEntry) mirrors the backend so the UI never hides a level the daemon would acceptOnly widens, never narrows. No combination that validates today starts failing. Tokens outside the catalog (Codex's
ultra) still fail closed. Codex is untouched — its empty model still fails closed per MUL-4347.Open decision
The union path means that if the CLI resolves to Sonnet and the user picked
xhigh, we pass--effort xhighdown for the CLI to handle. I could not establish from static analysis whether Claude clamps or hard-errors on a model-unsupported effort (testing live burns quota and wouldn't generalize across CLI versions).The conservative alternative is codex-style fail-closed, but that strips the effort setting from every empty-model Claude agent — a wider blast radius than the current bug. This PR takes the widening side; happy to flip it.
Verification
go test ./pkg/agent/... ./internal/metrics/...— passpnpm exec vitest run agents/components/inspector/— 37 passtsc --noEmitonpackages/views,gofmt/go vet/eslint— cleanThe two new frontend tests were run against the old implementation first to confirm they actually fail; they aren't passing vacuously.
Merge note
Overlaps #5910 in
claudeStaticModels()/models_test.go. If #5910 merges first, rebase this branch and change thedefaultsassertion inTestClaudeStaticModelsExposesOpus5todefaults != 0to match the other two catalog tests.