[BUG] Capability-string effort fall back sends `high` instead of the highest supported level at or below

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Aug 18, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When an effort level is not supported according to a model family's ANTHROPIC_DEFAULT_<FAMILY>_MODEL_SUPPORTED_CAPABILITIES string, Claude Code sends high rather than the highest supported level at or below the requested one.

model-config.md states:

If you set a level the active model does not support, Claude Code falls back to the highest supported level at or below the one you set. For example, xhigh runs as high on Opus 4.6.

Concretely, with CLAUDE_CODE_EFFORT_LEVEL=max and a capability string of effort,xhigh_effort,... (so xhigh is supported, max is not), the highest supported level at or below max is xhigh. The request sends effort: "high".

The fall back is also silent. In plain-text --print mode the entire output of a falling-back run is the model's reply, with nothing naming the requested or applied level. That contrasts with the organization-effort-limit clamp, which the docs say emits "a warning names the requested and applied levels" in interactive and plain-text --print sessions. I tested --print only; I have not checked whether an interactive session warns.

This is distinct from #49283, which covered xhigh requested on a model with no xhigh support at all, is closed, and is locked. The case here is a max request on a configuration where xhigh is both permitted and demonstrably reachable.

Scope note, stated plainly because it bounds who can act on this: model-config.md says these capability variables "take effect on third-party providers such as Amazon Bedrock, Google Cloud's Agent Platform, and Microsoft Foundry" and "have no effect when connecting directly to api.anthropic.com". Beyond that, every model in the docs' own effort table that supports xhigh also supports max, and the built-in predicates in the bundle agree, so a supports-xhigh-but-not-max model does not exist today except by constructing it with this override. The defect is therefore likely unreachable on the direct API by any route right now. It is still a real divergence from documented behavior on the third-party providers where the variables do apply, and it removes the only per-family lever those users have.

What Should Happen?

max requested against a capability string containing xhigh_effort but not max_effort should send effort: "xhigh", per the documented "highest supported level at or below" rule.

Failing that, the fall back should emit a warning naming the requested and applied level, and the documentation should describe the actual behavior rather than a step-down that only the organization-limit path performs.

Error Messages/Logs

# Row 2 of the table below. Capability string permits xhigh but not max; max was requested.
$ grep -o '"output_config":{[^}]*}' /tmp/eff.txt
"output_config":{"effort":"high"}

# Row 3, same capability string, xhigh requested directly. Proves xhigh is reachable here.
"output_config":{"effort":"xhigh"}

Steps to Reproduce

Requires a Bedrock (or other pinned-model-ID) setup, per the scope note above. Each step is one cheap -p call and reads the effort off the wire rather than trusting the /effort menu or the spinner label.

  1. Confirm the baseline. With the full capability set and max requested, max is sent:

``bash
echo "reply with just OK" | DEBUG=1 CLAUDE_CODE_DEBUG_LOGS_DIR=/tmp/eff1.txt \
CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose OTEL_LOG_RAW_API_BODIES=1 \
command claude -p --model "global.anthropic.claude-opus-5" \
--settings '{"env":{"CLAUDE_CODE_EFFORT_LEVEL":"max","ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES":"effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking"}}'
grep -o '"output_config":{[^}]*}' /tmp/eff1.txt
# "output_config":{"effort":"max"}
``

  1. Remove only max_effort, leaving xhigh_effort in place, and request max again:

``bash
echo "reply with just OK" | DEBUG=1 CLAUDE_CODE_DEBUG_LOGS_DIR=/tmp/eff2.txt \
CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose OTEL_LOG_RAW_API_BODIES=1 \
command claude -p --model "global.anthropic.claude-opus-5" \
--settings '{"env":{"CLAUDE_CODE_EFFORT_LEVEL":"max","ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES":"effort,xhigh_effort,thinking,adaptive_thinking,interleaved_thinking"}}'
grep -o '"output_config":{[^}]*}' /tmp/eff2.txt
# "output_config":{"effort":"high"} <-- expected "xhigh"
``

  1. Prove xhigh is reachable on that same capability string by requesting it directly:

``bash
echo "reply with just OK" | DEBUG=1 CLAUDE_CODE_DEBUG_LOGS_DIR=/tmp/eff3.txt \
CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose OTEL_LOG_RAW_API_BODIES=1 \
command claude -p --model "global.anthropic.claude-opus-5" \
--settings '{"env":{"CLAUDE_CODE_EFFORT_LEVEL":"xhigh","ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES":"effort,xhigh_effort,thinking,adaptive_thinking,interleaved_thinking"}}'
grep -o '"output_config":{[^}]*}' /tmp/eff3.txt
# "output_config":{"effort":"xhigh"}
``

Summary, with CLAUDE_CODE_EFFORT_LEVEL and the capability string as the only variables:

| Opus capability string | requested | wire effort | expected per docs |
|---|---|---|---|
| effort,xhigh_effort,max_effort,... | max | max | max |
| effort,xhigh_effort,... | max | high | xhigh |
| effort,xhigh_effort,... | xhigh | xhigh | xhigh |

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.235 (Claude Code)

Platform

AWS Bedrock

Operating System

macOS

Terminal/Shell

Other

Additional Information

Terminal is Ghostty, shell is zsh; neither appears in the dropdown.

The resolution logic in the shipped 2.1.235 bundle hardcodes high in both fall-back branches, which matches the observed behavior:

function JRd(e, t) {
  let r = e;
  if (typeof r === "string" && Ape(r)) r = MCe(r, t);   // organization-limit clamp: index-based step-down
  if (r === "max"   && !zje(t)) r = "high";             // unsupported max   -> high
  if (r === "xhigh" && !YDe(t)) r = "high";             // unsupported xhigh -> high
  return r
}

Note the asymmetry: MCe, the organization-effort-limit path, does perform a proper index-based step-down to the cap, which is what the documented sentence describes. The model-capability path does not. Reusing the step-down in the capability path would make behavior match the docs.

For the silence, the org-limit path is the only one with a warning attached. The single warning call site in the bundle is wired to GRd, the org-limit-exceeded message generator, and gates on output mode exactly as documented:

let ur = jLr(t.effort, a), Do = GRd(ur, Li);
if (Do !== null)
  if (D !== "json" && D !== "stream-json" && K.CLAUDE_CODE_SESSION_KIND !== "bg") Y4(Do);
  else T(`[effort] ${Do}`, { level: "warn" })

JRd's call sites have no equivalent, which is consistent with the observed silence in --print.

Recovered with strings "$(readlink -f ~/.local/bin/claude)" | grep -o 'function JRd([^)]*){.\{0,200\}'. Symbol names are minified and will differ between builds.

Prior art I checked before filing, none of which covers this case: #49283 asked for the fallback-to-high behavior to be documented for the xhigh-unsupported case and is closed and locked; #52534 reverse-engineered the separate Opus 4.7 launch-effort hold, which lives in JRd's caller TX and does not apply to Opus 5 or Sonnet 5, and is closed NOT_PLANNED; #54249 covered effortLevel dropping max and the picker ignoring the env var, and is closed COMPLETED; #35904 tracks effortLevel still rejecting max and is open; #73833 covers Fable 5 preserving effort across a model substitution, which is a different mechanism from level substitution within one model.

View original on GitHub ↗