[BUG] alwaysThinkingEnabled not translated to thinking:{type:"adaptive"} on Opus 4.8 — sessions silently run without thinking; 400 on xhigh effort with WebSearch
Summary
On Opus 4.8, Claude Code does not translate "alwaysThinkingEnabled": true (settings.json) into thinking: {type: "adaptive"} on outgoing API requests. Per the effort docs, on Opus 4.8 adaptive thinking is not on by default:
Set thinking: {type: "adaptive"} to enable thinking; without it, requests run without thinking.
Consequence: every Opus 4.8 session runs silently without extended thinking at effort ≤ high, and hard-fails at xhigh/max effort the moment a server-side tool call (WebSearch) is made:
API Error: 400 output_config.effort 'xhigh' is not supported when thinking is disabled on this model. Use effort 'high' or below, or enable thinking.
Fable 5 is unaffected because its adaptive thinking is always-on at the API level and requires no thinking configuration — which is precisely what isolates the bug to the client-side request construction for Opus 4.8.
Environment
- Claude Code Desktop App for Windows (auto-updated, current as of 2026-07-21), Windows 11 Pro
- Direct Anthropic connection (no gateway;
ANTHROPIC_BASE_URLempty), Max 5x plan ~/.claude/settings.json:"alwaysThinkingEnabled": true,"model": "opus","effortLevel": "medium"- Verified NO overrides:
MAX_THINKING_TOKENSabsent from process/user/machine env; no thinking keys in~/.claude.json; nosettings.local.json
Reproduction
- Set
"alwaysThinkingEnabled": truein~/.claude/settings.json, fully restart the app. - Start a session on Opus 4.8, set effort to xhigh (Extra) via the effort menu.
- Trigger any WebSearch tool call.
- →
API Error: 400 output_config.effort 'xhigh' is not supported when thinking is disabled on this model.
Switch the same session to Fable 5 (same xhigh effort, same settings) → WebSearch succeeds.
Verified matrix (all on 2026-07-21, same machine, same settings.json)
| Model | Effort | WebSearch result |
|---|---|---|
| Opus 4.8 | xhigh | 400 thinking-disabled (reproduced twice, before AND after full app+OS restart) |
| Fable 5 | xhigh | OK |
| Fable 5 | high | OK |
The app/OS restart made no difference — this rules out stale in-memory session state and isolates the variable to the model, i.e. to the request construction path that should be sending thinking: {type: "adaptive"} for Opus 4.8 (and 4.7) when alwaysThinkingEnabled is true.
Expected
alwaysThinkingEnabled: true should result in thinking: {type: "adaptive"} being sent on Opus 4.8/4.7 requests, so that (a) sessions actually use extended thinking, and (b) xhigh/max effort tiers work.
Actual
- Effort ≤ high: session runs without thinking, silently — no error, no indication, while the user believes thinking is on.
- Effort xhigh/max: WebSearch (server-side tool) calls fail with the 400 above. Main-loop turns and subagents continue to work, so the failure surfaces only on that code path.
Related issues
- #13532 — alwaysThinkingEnabled not respected since v2.0.64 (same setting silently ignored; this report adds the model-dependence and the 400-at-xhigh signature)
- #17285 — /config toggle removes the key from settings.json and never recreates it
- #68797 — subagent code path hard-sets
thinking: {type: "disabled"}(same class: request construction ignoring session thinking config)
Showing cached comments. Read the full discussion on GitHub ↗
9 Comments
Environment details (missing from the original report):
ANTHROPIC_BASE_URLempty), Claude Max 5x planReminder of the repro matrix (same session, same settings.json with
alwaysThinkingEnabled: true):400 output_config.effort 'xhigh' is not supported when thinking is disabled on this model(reproduced twice, before and after full app restart)Consistent with the client not sending
thinking: {type: "adaptive"}on Opus 4.8 despitealwaysThinkingEnabled: true— per the effort docs, without that field "requests run without thinking", silently at effort ≤ high, erroring only at xhigh/max.Version clarification: the Desktop App is a two-component install — the Store shell (Electron, 1.22209.3) and the Claude Code runtime it auto-downloads into
%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude-code\<version>. The runtime version actually running the reported sessions is 2.1.215 (2.1.209 also present on disk from a previous auto-update). So the relevant client version for this bug is claude-code 2.1.215, not just the Store shell version.Re-tested on updated versions — bug still present.
Environment (2026-07-22):
Result: identical 400 on WebSearch:
Only one variable changed between the 2026-07-21 matrix and this test (app/runtime version); model and effort were fixed before the test. The client still does not send
thinking: {type: "adaptive"}for Opus 4.8 despitealwaysThinkingEnabled: truein settings.json, so sessions run without thinking and xhigh/max effort levels remain unusable on this model.Independent repro on macOS with a cross-model control matrix, plus what looks like the proximate cause in the bundled model table.
Env: claude-code
2.1.218, CLI (not Desktop), macOS 15 (Darwin 24.6.0),~/.claude/settings.jsonhas"effortLevel": "xhigh".Repro command
Each row below is a separate non-interactive subprocess, so nothing leaks between runs:
Matrix
| model | effort | extra | result |
|---|---|---|---|
|
claude-opus-4-8[1m]| xhigh | — | ❌ 400 ||
claude-opus-4-8| xhigh | — | ❌ 400 ||
claude-opus-4-8[1m]| xhigh |MAX_THINKING_TOKENS=32000| ❌ 400 ||
claude-opus-4-8[1m]| high | — | ✅ works ||
claude-sonnet-5| xhigh | — | ✅ works ||
claude-fable-5| xhigh | — | ✅ works |Error is always:
Two things worth adding to what's already in the thread:
claude-opus-4-8fails identically toclaude-opus-4-8[1m].MAX_THINKING_TOKENSdoes not work around it either. Setting it >0 flips the session-level thinking config ({type:"enabled",budgetTokens:N}in the startup path) but the request that carries theweb_searchserver tool still goes out with thinking disabled. So there is currently no client-side knob that fixes this —alwaysThinkingEnabled(per OP) andMAX_THINKING_TOKENSboth fail, leaving only "drop to high" or "switch model".Possible proximate cause
Grepping the bundled model capability table in the 2.1.218 binary:
Fable 5 carries
rejects_disabled_thinking; Opus 4.8 does not. That lines up with Fable 5 being the one model in the matrix that never hits this — the client knows it must not send disabled thinking for Fable 5, and has no such constraint recorded for Opus 4.8, while the API side evidently does enforce it for Opus 4.8 at xhigh.Caveat so this isn't over-claimed:
claude-sonnet-5also lacksrejects_disabled_thinkingand works fine at xhigh, so the missing flag alone isn't sufficient to cause the failure — the server-side constraint is model-specific and Opus 4.8 is the one where client table and server constraint disagree. Either adding the capability for Opus 4.8, or fixing the underlying "thinking config isn't propagated onto server-tool requests" path, would resolve it.Also worth noting:
default_effortforclaude-opus-4-8in that same table is"high", so this only bites users who explicitly raise effort to xhigh/max.<sub>Matrix produced with Claude Code; every row was executed, not inferred.</sub>
@KevinZhangv5's capability-table finding and the "no client-side knob fixes this" matrix match what I arrived at independently, so I won't restate any of it. Three small additions instead.
1. Reproduces on Opus 5 at effort
maxOpus 5 shipped after that matrix was posted, so for the record — Claude Code 2.1.219, Linux:
Same error, new model, and
maxbehaves likexhighhere.One practical note that cost me a control run:
CLAUDE_CODE_EFFORT_LEVELtakes precedence over--effort. If the env var is set to something else, the flag is silently ignored and the run looks like a pass. Worth anenv -uwhen building matrices like yours.2. WebFetch is a useful control for the "not propagated onto server-tool requests" theory
WebFetch also issues a secondary request from the client, but under identical conditions it never fails:
| tool |
--model claude-opus-5, effortmax||---|---|
| WebSearch | ❌ 400 |
| WebFetch | ✅ works |
The observable difference is that WebFetch's secondary request runs on the small/fast model and does not carry the session effort, while WebSearch's carries the main-loop model and the session effort while forcing thinking off.
That's consistent with your theory and narrows it usefully: the problem isn't "server-tool requests" as a category, it's specifically the path that inherits the main model and the session effort and hardcodes thinking disabled. WebFetch shows that a secondary request which drops either of those two ingredients is fine.
3. This error looks like it was meant to be recovered automatically
The client appears to have a retry path that strips
output_config.effortand retries when the server rejects it. It doesn't fire here — the error reaches the user instead of being retried away. From the outside, the detection looks keyed to older phrasings and doesn't match the wording the server currently returns (...is not supported when thinking is disabled on this model).If that's right, it's a second and independent defect: even once the thinking-propagation issue is fixed, any future change to the server's error text would silently disable that safety net again, and users would see raw 400s for a condition the client already knows how to handle. Matching on a machine-readable field rather than on message text would make it durable.
This one seems worth splitting out — happy to file it separately if a maintainer would prefer that, since it isn't specific to WebSearch or to Opus 4.8.
✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
Still reproduces on Opus 5, and on the Desktop App / Windows side of the matrix.
@carrotRakko's Opus 5 run was CLI on Linux at effort
max. Here is the complementary cell: Desktop App (GUI) on Windows, Opus 5, effortxhigh— same runtime version, different channel and different effort level.Environment (2026-07-25):
claude-opus-5, effortxhigh("Extra" in the GUI selector)ANTHROPIC_BASE_URLempty), Claude Max 5xsettings.jsonstill hasalwaysThinkingEnabled: trueResult: first WebSearch call of the session fails with the same 400:
Two things this adds to the thread, stated no more strongly than the evidence supports:
max-specific. The Opus 5 failure was so far only shown viaclaude -pon Linux atmax; it also happens in the Desktop GUI on Windows atxhigh. Both raised effort levels behave the same on both channels.rejects_disabled_thinkingbut the server enforces the constraint" — rather than with anything specific to one model id.Caveat on variables: relative to my 2026-07-22 test, both the model (4.8 to 5) and the versions (runtime 2.1.217 to 2.1.219, shell 1.24012.1.0 to 1.24012.9.0) changed, so this run does not isolate a cause — it only shows the bug survives all three changes. The
--effort/CLAUDE_CODE_EFFORT_LEVELprecedence gotcha does not apply here: the effort came from the GUI selector, and there is noCLAUDE_CODE_EFFORT_LEVELin this environment.Still no maintainer response on this issue, and it is now three independent reporters across Windows/macOS/Linux and Opus 4.8/Opus 5, with no client-side workaround other than lowering effort or switching model. +1 to @carrotRakko splitting out the "retry path keyed on error message text" observation as its own issue — it is a separate defect from the thinking-propagation one.
Reproduced on Opus 5 / CC 2.1.220 / Linux CLI — plus a workaround correction and a likely reason some repro attempts come back clean
Adding a data point on a model not yet in this thread, and correcting a workaround.
Environment — claude-code
2.1.220(CLI, native install), Linux7.0.0-28-generic, subscription via OAuth, direct API (ANTHROPIC_BASE_URLempty).alwaysThinkingEnabled: true,showThinkingSummaries: true. Effort raised toxhighvia theenvblock; each row below is a separate non-interactiveclaude -pprocess, n=2 per row, identical results both passes.| # | call site | model | effort | result (n=2) |
|---|---|---|---|---|
| 1 | main thread,
WebSearch| opus-5 | xhigh | ❌ 400, 2/2 || 2 | ad-hoc
general-purposesubagent,WebSearch| opus-5 | xhigh | ❌ 400, 2/2 || 3 | subagent with
effort: highin agent frontmatter | opus-5 | session xhigh | ❌ 400, 2/2 — error still sayseffort='xhigh'|| 4 | subagent with
model: sonnet| sonnet-5 | session xhigh | ✅ ok, 2/2 || 5 |
WebFetch, main thread | opus-5 | xhigh | ✅ ok, 2/2 |Verbatim:
This happens with
alwaysThinkingEnabled: true— consistent with the OP's finding that the setting never reaches the outgoing request.Workaround correction (row 3). The
effort: highfrontmatter workaround was verified in #76689 on a skill; it does not generalize to agent frontmatter. An agent pinned toeffort: highstill fails, and the error still reportseffort='xhigh'— the server-tool sub-request carries the session's effort, not the delegate's. What did work in the same sessions: a delegate on another model (row 4) andWebFetch(row 5).Why some repro attempts come back clean — worth checking before reporting a negative. An
env: { "CLAUDE_CODE_EFFORT_LEVEL": ... }block insettings.jsonoverrides the--effortflag. I ran ~20 probes settingxhighon the command line while the settings file pinnedauto, and every one passed — the session never lefthigh. Measured: with the env pinned,--effort lowvsxhighgave 911/953 vs 798/533 output tokens (no separation); with it unset, medians 722 vs 1053. A green matrix can mean "the level never applied". Confirming it took hold is non-obvious:$CLAUDE_EFFORTand the hook-inputeffort.levelboth readhighinclaude --printregardless of how the level was set — the reliable checks are token spend or the interactive statusline.Detection note for auditing past runs: the failure is silent — the error lands in the
WebSearchtool_resultbody with nois_errorflag. In my two passes it showed up in 5 transcript files, 2 of them subagent transcripts one directory deeper:---
Update 2026-07-26 — the trigger is the tier, not how the level was set. Plus a correction to my own oracle advice above.
Every row in the matrix above raised the level through the
envblock, so that matrix could not separate "the env layer" from "the tier". I re-ran it varying both, same CC2.1.220, headless, one prompt. Two things make this pass stronger than the previous one: the oracle is read straight off--output-format stream-jsoninstead of a guessed transcript path, and the 400 names the effort it rejected — so a failing run states its own level and a green row can no longer quietly mean "the level never applied". Each row also carries a control thatWebSearchwas actually called.| effective tier | how it was raised |
WebSearch||---|---|---|
|
high|--effort high| ✅ 0/2 failures — returned 7 results ||
xhigh|effortLevelinsettings.json, opus-5 | ❌ 3/3 ||
xhigh|effortLevelinsettings.json, opus-5[1m] | ❌ 4/4 ||
xhigh|--effort xhigh| ❌ 3/3 ||
max|--effort max| ❌ 2/2 |12/12 failures at
xhigh/max, 0/2 athigh. Tier quoted in the errors:'xhigh'×20 and'max'×4 — two rejections per failing run. No mechanism avoids it:effortLevel, the--effortflag andultracode(which resolves toxhigh) fail the same way. Theenvblock is not special — it is just one way of arriving atxhigh. Two controls keep this honest:--effort highbeateffortLevel: xhighfrom the same settings file, so the flag row really is the flag and not the file leaking through; and the mere presence of aneffortLevelkey breaks nothing — the passinghighrow ran against that same file.This lines up with the documented API rule rather than contradicting it:
thinking: {"type":"disabled"}is accepted only at efforthighor below, and the server-tool sub-request omits the thinking config while carrying the session's current effort. Anything abovehighis therefore rejected by construction, however the level got there.Correction to the last sentence of "Why some repro attempts come back clean". I wrote there that
$CLAUDE_EFFORTand the hook-inputeffort.levelreadhighinclaude --printregardless of how the level was set, and recommended token spend or the statusline instead. The first half did not survive re-measurement: on 2.1.220$CLAUDE_EFFORTread backhigh,xhighandmaxexactly as set — fromeffortLeveland from--effortalike — and matched the tier quoted in the 400 every time. (I did not re-test the env path, so that case is still open.) The second half is worse advice than I thought: token spend separates tiers poorly —--effort lowvsxhighoverlapped completely on a short prompt (n=3 each), and on a heavy task medians differed ~25% with crossing ranges. The cheap, reliable oracle for this bug is the error text itself, since it names the effort.The rest of the comment stands: rows 1–5, the
effort:agent-frontmatter workaround still failing,model: sonnetandWebFetchstill working, the silent-failure detection note, and the fact that anenv-block level overrides--effort(that precedence is real — it just is not the cause of this bug).---
**Update 2 (2026-07-26) — the ceiling is Opus 5's,
effort:in agent frontmatter does work, and my detection note above was wrong.**Further measurement after the first update. Same CC
2.1.220, same oracle: read off--output-format stream-json, with the tier quoted in the API rejection as the level oracle and a control thatWebSearchwas actually invoked in every row.1. This is an Opus 5 ceiling, not a Claude Code one. As the session model at
xhighandmax:| session model |
xhigh|max||---|---|---|
|
claude-opus-5| ❌ | ❌ ||
claude-opus-5[1m]| ❌ | — ||
claude-sonnet-5| ✅ | ✅ ||
claude-fable-5| ✅ | ✅ |0/8 failures on Sonnet 5 and Fable 5, real result lists returned in every run. Consistent with the documented rule that
thinking: {"type":"disabled"}is accepted only at efforthighor below on this model — the server-tool sub-request omits the thinking config, so on Opus 5 anything abovehighis rejected by construction. Sonnet 5 and Fable 5 do not carry that restriction. (Their lower tiers were not run — no reason to expect trouble there.)2. Correction to my "workaround correction" in row 3 above. I reported that
effort: highin agent frontmatter still failed. It does work — my original probe had the session level pinned throughCLAUDE_CODE_EFFORT_LEVEL, and that outranks agent frontmatter, so the delegate never actually leftxhigh. With the level set any other way, the delegate's owneffort:governs. Seven rows, session onclaude-opus-5atxhigh, each measured through YAML frontmatter in.claude/agents/*.md:| agent frontmatter | ran on |
WebSearch||---|---|---|
|
effort: high| opus-5 | ✅ 2/2 ||
model: claude-sonnet-5| sonnet-5 | ✅ 2/2 || nothing declared | opus-5 | ❌ 2/2 |
|
effort: xhigh| opus-5 | ❌ 2/2 ||
tools:narrowed,WebSearchomitted | sonnet-5 | ❌ 2/2 — tool absent, never called ||
effort: high, session pinned by env | opus-5 | ❌ 2/2 ||
model: claude-sonnet-5, session pinned by env | sonnet-5 | ✅ 2/2 |So the server-tool sub-request carries the delegate's effective level, not the session's — the opposite of what I concluded above. Two consequences worth knowing: a delegate that declares no
effort:inherits the session's level (discriminating control: the same undeclared delegate fails 2/2 from anxhighsession and searches 2/2 from ahighone), andCLAUDE_CODE_EFFORT_LEVELoverrides frontmatter in both directions — an envlowdrags a delegate declaredxhighdown tolow, 2/2. The same escape exists for a built-in delegate through the Agent tool's per-callmodeloverride:general-purposespawned plainly from anxhighsession failed 2/2, withmodel: sonnetit searched 2/2.3. My detection note above is wrong, and this is the part worth fixing in anyone's tooling. I wrote that the error "lands in the
WebSearchtool_resultbody with nois_errorflag". It does carry the flag. Across the full corpus — 30 of 30 failing runs, session-level and delegate-level alike — thetool_resulthasis_error: trueand a body prefixed:So a scan of
tool_resultrecords does find it. The silence is real but sits in two other places: there is no assistant-level API-error record, so a filter onisApiErrorMessagedoes not see it; and a parent session sees only a delegate's final text, never itstool_results — which is why a research subagent can return a confident report with a whole source tier missing and nothing surfaces upward. That second one is the failure mode that actually costs users.Net for maintainers: the trigger is the tier and the tier ceiling belongs to Opus 5; a per-delegate
effort:ormodel:pin is a real workaround unless the level comes from the environment variable; and the failure is detectable ontool_result, just not through the assistant-level error path or from the parent of a subagent.Doesn't work for me with opus 5 on 2.1.220 as well!
Opus 5 at
xhighfails with no tools available at all — noWebSearch, noserver-side tool, no delegate. Posting because that's a different call path from
everything measured above, not another +1.
Environment
~/.claude/settings.json:"alwaysThinkingEnabled": true(top level)envblock in any of the four settings files;MAX_THINKING_TOKENS,DISABLE_INTERLEAVED_THINKINGandCLAUDE_CODE_EFFORT_LEVELall absent — so theenv-overrides-
--effortprecedence gotcha from the updates above does not apply--effort, each row a separate processMatrix —
--allowedTools ""in every row, so no tool can be called:claude -p "Reply with just the word: ok" --effort <TIER> --model <MODEL> --allowedTools ""
| model | effort | result |
|---|---|---|
|
claude-opus-5| xhigh | ❌ 400 ||
claude-opus-5| high | ✅ok||
claude-opus-4-8| xhigh | ✅ok|Verbatim:
API Error: 400 output_config.effort 'xhigh' is not supported when thinking is
disabled on this model. Use effort 'high' or below, or enable thinking.
Why this looks like a distinct defect
@nikitaCodeSave's Update 2 pins the trigger to the server-tool sub-request omitting
the thinking config while carrying the session's effort, and
WebFetchpassing isconsistent with that. But with
--allowedTools ""there is no sub-request to blame —this is the main-loop request going out with thinking disabled at
xhigh, despitealwaysThinkingEnabled: true.If that's right, the server-tool path is one symptom of a broader propagation failure
rather than the whole bug, and the per-delegate
effort:/model:workarounds inUpdate 2 don't apply, since no delegate is involved. The only escape here is dropping
to
highor changing model.Also note this is the inverse of the OP's model split: Opus 4.8 passes at
xhighfor me while Opus 5 fails. Same direction as @sp4mmami's "it followed the model,
it wasn't an Opus 4.8 quirk", and consistent with @KevinZhangv5's capability-table
reading — but the specific failing model has changed since the OP.
Limits of this evidence
the tier, change the model → passes), but I have not repeated them.
thinkingfield isliterally absent — the 400 is my only evidence for that, same as the rest of the
thread.
silently mean "the level never applied".