Repository navigation
fix: clamp unsupported max variant for non-Opus Claude models - #2606
code-yeongyu merged 2 commits into
Conversation
…back When an agent configured with variant: "max" falls back from Opus to Sonnet (or Haiku), the "max" variant was passed through unchanged. OpenCode sends this as level: "max" to the Anthropic API, which rejects it with: level "max" not supported, valid levels: low, medium, high The anthropic-effort hook previously only handled Opus (inject effort=max) and skipped all other Claude models. Now it actively clamps "max" → "high" for non-Opus Claude models and mutates message.variant so OpenCode doesn't pass the unsupported level to the API.
There was a problem hiding this comment.
No issues found across 2 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Auto-approved: The fix correctly handles variant clamping for non-Opus Claude models to prevent API errors, with appropriate test coverage and no apparent side effects.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9346bc8379
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Update: After further testing, we found a cleaner approach that works as a standalone OpenCode plugin ( // Anthropic: "max" is Opus 4.6 only — clamp to "high" for Sonnet/Haiku
if (message.variant === "max" && isClaude(model.providerID, model.modelID)) {
if (!OPUS_PATTERN.test(model.modelID)) {
message.variant = "high";
}
}However, the fix in this PR is still valuable as a built-in safety net so users don't need a separate plugin. The two approaches are complementary — the PR catches it in the effort hook, the plugin catches it even earlier. Also filed anomalyco/opencode#17736 for OpenCode itself to validate thinking levels against model capabilities at the provider layer. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@acamq please check it again :) |
Summary
This PR is a narrow compatibility fix, not a general model-fallback system.
The model has already been selected.
The actual problem is that the request can still carry a variant level that the selected model does not support.
For Claude models,
variant: "max"is valid on Opus 4.6, but not on Sonnet or Haiku. When that unsupported level is passed through unchanged, Anthropic rejects the request with:So the failure is not “the wrong model was chosen”.
The failure is “the chosen model received a thinking/variant level it cannot accept”.
Problem
When an agent with
variant: "max"(for example Prometheus or Atlas) moves fromclaude-opus-4-6toclaude-sonnet-4-6, the request keeps"max"even though Sonnet does not support that level.That makes fallback and recovery flows brittle:
Root cause
The
anthropic-efforthook insrc/hooks/anthropic-effort/hook.tshandled the Opus case, but skipped non-Opus Claude models:variant: "max"-> injectseffort=maxvariant: "max"-> returns early and leavesmessage.variant="max"untouchedThat allows OpenCode to send the unsupported level straight to Anthropic.
Fix
When the selected Claude model is not Opus and the request carries
variant: "max", this PR now:"high"message.variantto"high"That keeps the request compatible with Sonnet/Haiku while preserving the intent to use the highest supported level.
Why this matters
Without this fix, fallback looks broken even when the fallback model itself is perfectly usable.
The system chooses a model that could work, then immediately breaks the request by carrying over a level that model does not support.
This PR fixes that narrow but important compatibility gap.
Scope
This PR intentionally stays small and targeted.
It does not implement a full general-purpose resolver for all model-specific settings such as:
variantreasoningEffortthinkingmaxTokensThat broader problem should be solved separately as a dedicated settings compatibility resolver that runs after model selection and independently from model fallback.
Changes
src/hooks/anthropic-effort/hook.tsvariant: "max"to"high"src/hooks/anthropic-effort/index.test.ts