effortLevel "max" silently dropped from settings.json, and /effort picker ignores CLAUDE_CODE_EFFORT_LEVEL
Summary
On a model that supports max effort (Opus 4.7 / Opus 4.6 / Sonnet 4.6), there are two related bugs in the effort plumbing:
settings.jsonrejects"max"— setting"effortLevel": "max"is silently dropped by the persisted-settings validator, even thoughmaxis a valid value everywhere else (--effortCLI flag,CLAUDE_CODE_EFFORT_LEVELenv var,/effort maxinteractive picker, model capability checks).
/effortinteractive picker ignoresCLAUDE_CODE_EFFORT_LEVEL— even when the env var is correctly applied to API requests, the picker still shows the persistedsettings.effortLevelvalue, which is misleading.
These two bugs interact in a confusing way: the documented workaround for #1 (set the env var instead) makes the API behavior correct, but #2 then makes the picker lie about the active level, so the user thinks the workaround failed.
Version
Claude Code 2.1.121 (macOS arm64).
Bug 1: settings.json rejects "max"
Steps to reproduce
- In
~/.claude/settings.json:
``json``
{ "effortLevel": "max" }
- Restart Claude Code on Opus 4.7.
- Run
/effort current.
Expected: Current effort level: max
Actual: falls back to model default xhigh — the persisted value is silently discarded.
Root cause
The validator used to read settings.effortLevel is whitelisted to four values and drops max:
function iK_(H) {
if (H === "low" || H === "medium" || H === "high" || H === "xhigh") return H;
return; // -> undefined for "max"
}
function nk1() { return iK_(I8().effortLevel); }
The canonical effort enum elsewhere in the binary is:
YB = ["low", "medium", "high", "xhigh", "max"];
Other entry points correctly accept max:
XXH()— env varCLAUDE_CODE_EFFORT_LEVEL→ur()→ usesgQ_(YB)allowlistA_8()— interactive/effortpicker selection →ur()--effortCLI flag — explicit["low","medium","high","xhigh","max"]validator- Per-model capability check
lK_()already gatesmaxto Opus 4.7 / Opus 4.6 / Sonnet 4.6
Suggested fix
Replace iK_ with the same validator the env-var path uses (ur / gQ_), so settings.effortLevel and CLAUDE_CODE_EFFORT_LEVEL accept the same set of values.
Bug 2: /effort picker ignores CLAUDE_CODE_EFFORT_LEVEL
Steps to reproduce
- Set in
~/.claude/settings.json:
``json``
{
"effortLevel": "xhigh",
"env": { "CLAUDE_CODE_EFFORT_LEVEL": "max" }
}
- Restart Claude Code on Opus 4.7.
- Run
/effort(no args, opens picker).
Expected: picker shows max selected (the live, env-resolved level).
Actual: picker shows xhigh selected (the persisted settings.effortLevel, ignoring the env var).
/effort current correctly reports max — only the picker is misleading.
Root cause
At session startup, appState.effortValue is initialized as:
effortValue: A_8(w.effort) // w.effort = --effort CLI flag, often undefined
function A_8(H) {
let _ = ur(H);
if (_ !== void 0) /* unpin opus-4-7 launch effort */;
return _ ?? nk1(); // -> iK_(settings.effortLevel), env var never consulted
}
The picker (o13) reads from appState.effortValue directly:
function e13(H) { return H.effortValue }
// picker: K = w_(e13) -> appState.effortValue
Whereas the footer text and /effort current go through db() → abH(), which does check the env var:
function abH(H, _) {
// ...
let O = XXH(); // CLAUDE_CODE_EFFORT_LEVEL
// ...
let T = O ?? (q ? K : void 0) ?? _ ?? K;
// ...
return T;
}
So API requests use the env-resolved value, but the picker UI shows the stale persisted value. Confusing, because the user opens /effort to verify their config and sees the "wrong" level.
Suggested fix
Have the picker resolve the displayed current selection through the same path the footer uses (db / abH), so env var overrides are reflected in the UI. Or, on session init, seed effortValue from XXH() ?? A_8(w.effort) so the env var participates in the initial state.
Workaround
Until #1 is fixed, set the env var via settings.json:
{
"effortLevel": "xhigh",
"env": { "CLAUDE_CODE_EFFORT_LEVEL": "max" }
}
This makes API requests use max. Until #2 is fixed, ignore the picker and verify with /effort current (which correctly reflects the env-resolved level).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗