effortLevel in settings.json silently discards "max" and "ultracode"

Status Open
Maintainer reply None cached
Activity 3 comments · opened Jul 22, 2026

Setting "effortLevel": "max" in settings.json is silently discarded, and the session starts at the default high with no warning that the value was rejected.

The effortLevel key is validated against enum(["low","medium","high","xhigh"]).optional().catch(void 0). Because of .catch(void 0), any value outside that enum is not a validation error: it produces no message and is coerced to undefined. But max is a real, selectable runtime effort tier. The full tier set is low, medium, high, xhigh, max, plus an ultracode tier (xhigh + workflows). So the persisted enum is a strict subset of the runtime tiers, excluding max and ultracode, and there is no supported way to persist max (or ultracode) as a launch default through settings.json.

For contrast, max is reachable at runtime two other ways, both session-only:

  • /effort (its own help text labels max "this session only").
  • CLAUDE_CODE_EFFORT_LEVEL=max (a per-session override that saves nothing).

Both were confirmed to produce genuine max effort. Only the persisted effortLevel path drops it.

Steps to reproduce

  1. Set "effortLevel": "max" in ~/.claude/settings.json.
  2. Start a new session.

Expected

Either the session launches at max, or a visible diagnostic states that max is not a persistable effortLevel value and is being ignored.

Actual

Effort is high (the default). No warning anywhere that max was rejected.

Suggested fix

Either or both:

  1. Emit a warning on out-of-enum values. .catch(void 0) swallows invalid input silently; a diagnostic ("max" is not a valid effortLevel; ignoring) closes the silent-failure gap regardless of whether max becomes persistable.
  2. Resolve the persistence gap. Extend the persisted enum to accept max (and ultracode) so it can be set as a launch default, or, if max is intentionally session-only, document that effortLevel does not accept it and point users to /effort or CLAUDE_CODE_EFFORT_LEVEL.

Environment

Claude Code v2.1.x

Severity: low. A UX papercut plus a small feature request, not a critical bug. The silent discard is the annoying part, since nothing tells the user their setting was ignored.

View original on GitHub ↗

3 Comments

yourstrulyeden · 1 month ago

Confirming this on 2.1.246, and adding a few things the report doesn't cover: how it presents to someone who doesn't already know about the enum, a display behaviour that makes it hard to self-diagnose, evidence that it isn't only a cold-start issue, and an argument against the low severity.

It doesn't present as a settings problem

settings.json here has "effortLevel": "max". The session runs high. But the symptom a user sees isn't "my settings key was ignored" — it's that the effort level keeps resetting. You pick max in the picker, work for a while, restart, and you're back on high with no indication anything changed.

It was treated as a persistence bug here for two days before this issue turned up. The title accurately describes the cause, which is unfortunately not what someone hitting it would think to search for.

The effort control keeps displaying the selected value

This is the part that makes it impossible to self-diagnose, and it's worth separating from the settings bug because it may be a distinct one.

After a restart the effort control still displays the previously-selected level while the session runs high. Checked directly on 25 Aug: the control read "Effort (Max)" with the slider at position 5 of 5, while that same session's request records from three seconds earlier read high. So the obvious way to verify the setting — looking at it — returns the wrong answer, which is presumably why this gets reported as a resetting problem rather than a settings one.

A check that does work: the request records carry the resolved value.

grep -o '"effort":"[a-z]*"' <session>.jsonl | tail -5

Across one restart today, from the same session's records:

13:38  max
13:42  max      <- last request before restart
13:45  high     <- first request after, on 2.1.246

More than one trigger

Three occurrences here across two sessions, on 24 and 26 August. The one above followed an extension update. Another followed an in-editor Reload Window, and a third followed an ordinary restart. So it isn't limited to a cold start — the reload case is the awkward one, since reloading is routine advice for picking up settings changes.

On severity

Marked low as a UX papercut. Two things arguably push it higher, though neither is dramatic:

  1. The workaround may have a recurring cost on long sessions. In the one instance I have instrumented, re-selecting the effort level mid-conversation coincided with a large cache write — the whole conversation appearing to be re-read on the next request. n=1, so I'd treat that as a lead rather than a finding, but if the resolved effort value does form part of the cached prefix then re-picking it after every restart is cheap on a fresh session and progressively less cheap on a long one.
  1. It silently downgrades work that was chosen to be expensive. Someone who sets max has decided a task warrants it. Running that task at high produces a plausible result at a quality level the user didn't pick, and as far as I can tell nothing surfaces that.

Both suggested fixes would help

The diagnostic on out-of-enum values would have saved those two days on its own, regardless of whether max ever becomes persistable.

One note on the stopgap: CLAUDE_CODE_EFFORT_LEVEL works, but it isn't equivalent to a persisted default — while it's set, /effort reports that the environment variable still controls the session. So it trades the reset problem for losing the runtime picker, which won't suit anyone who moves between effort levels deliberately.

atahan150 · 1 month ago

Confirming this is still present on v2.1.258 — the persisted effortLevel enum is still ["low","medium","high","xhigh"] with .catch(void 0), so "max" is silently coerced to undefined with no diagnostic.

Adding a concrete use case that runs into this, plus a second layer worth flagging:

Use case: I want some projects to always run at max, while my global default stays lower. Two gaps compound and make this impossible today:

  1. max can't be persisted at all through effortLevel (this issue).
  2. The effort level persists per-model (under modelSettings, e.g. {"claude-opus-4-8": {"effortLevel": "high"}}), not per-project — so even xhigh can't be scoped to a single project independently of the model.

So a project-level .claude/settings.json can't express "this repo runs at max (or even xhigh) while others stay at medium." A persistable, per-project effort level (including max) would close both gaps.

Minor UX note: in the /effort TUI, the footer offers Enter to confirm (persist) alongside s for this session only. For max, Enter doesn't actually stick — only s does. So the picker presents a persist affordance for a value that can't be persisted, which is exactly the silent-discard behavior described here, surfaced in the UI.

Agree with the suggested fix: at minimum emit a warning on out-of-enum values instead of swallowing them.

yourstrulyeden · 1 month ago

Confirming on 2.1.258, and adding one thing that I think simplifies the framing rather than adding to it.

@atahan150 describes two gaps that compound, max not being persistable through effortLevel and effort persisting per model rather than per project. Reading the shipped schema, I do not think those are two gaps.

I walked claude-code-settings.schema.json in anthropic.claude-code-2.1.258-win32-x64 for every effortLevel enum anywhere in it. There are exactly two, and they are identical:

effortLevel ["low", "medium", "high", "xhigh"]
modelSettings.<model>.effortLevel ["low", "medium", "high", "xhigh"]

Neither accepts max. So max is not scoped wrongly, and it is not merely missing from the top level key. It has no persisted representation anywhere in the settings schema, and both routes drop it the same silent way.

That matters for the suggested fix. Extending only the top level enum would leave modelSettings still discarding max without a diagnostic, so anyone who had set it per model would see the same behaviour and no message, on a version where the issue was marked fixed.

One data point on my own setup, offered as a control rather than a finding. My effortLevel is high, and my session request records agree, so the enum does exactly what it says for a value inside it. The discard is only ever silent for values outside it, which is what makes it hard to notice.