anthropics / anthropics/claude-code

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

Aperta
#87,788 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
api:bedrock area:model bug has repro platform:macos stale
Lingua principale
Python
Stelle
145k
Fork
23.1k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

### 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.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
aws, zsh
Ambito
cli, cloud
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.