anthropics / anthropics/claude-code
[BUG] Capability-string effort fall back sends `high` instead of the highest supported level at or below
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) 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__MODEL_SUPPORTED_CAPABILITIES` string, Claude Code sends `high` rather than the highest supported level at or below the requested one.
[model-config.md](https://code.claude.com/docs/en/model-config.md#adjust-effort-level) 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
```shell
# 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"}
```
2. 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"
```
3. 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:
```js
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:
```js
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.
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce the issue with the three Bedrock commands and inspect the emitted output_config effort values in the debug logs. Start by examining the shipped bundle's JRd resolution logic and its call sites, then verify that a max request with xhigh_effort but without max_effort resolves to xhigh; any warning behavior should match the documented fallback expectations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, zsh
- Domain
- cli, cloud
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100