[BUG] /model and /effort mutate global settings.json — breaks agents/fleet view; no way to configure per-agent model or effort

Status Open
Reported on v2.1.169
Maintainer reply None cached
Activity 9 comments · opened Jun 9, 2026

Summary

/model and /effort write to ~/.claude/settings.json immediately and globally. In the agents/fleet view (claude agents) this means there is no supported way to run a fleet of agents with intentional, independent model and effort configurations.

Environment

  • Claude Code: 2.1.169
  • Platform: Windows 11

The problem

~/.claude/settings.json is a single shared file. Every Claude Code session reads from it on start. /model and /effort both write to it immediately on invocation.

The slash commands look and feel like session controls, not "save my settings" commands. In a single session this is a confusing default. In the agents/fleet view it becomes a correctness bug:

  1. You have three agents running: one on Opus doing deep analysis, one on Sonnet doing fast search, one on Haiku doing summarisation.
  2. The Sonnet agent completes a phase and a skill invokes /effort high.
  3. Any agent started after that point inherits high effort on whatever model that last write left behind.
  4. There is no warning. There is no way to know which agent last mutated the shared state.

Launch ergonomics compound this: when you spawn a new agent from the fleet view, it inherits whatever settings.json currently contains — not the model or effort you intended when you hit "new agent." There is no way to launch an agent with an explicit, intentional model/effort configuration other than CLI flags, which must be known before the session starts and cannot be changed once running.

Note: v2.1.169 fixed background agents ignoring ANTHROPIC_MODEL env values — confirming that even the env var workaround for model configuration was broken for background agents until yesterday.

Steps to reproduce

  1. Open claude agents
  2. Launch agent A on Opus — note current model/effort
  3. Launch agent B — in that session run /model and select Sonnet
  4. Launch agent C from the fleet view
  5. Agent C starts on Sonnet, not Opus

Expected behaviour

  • /model and /effort should be session-local by default and should not write to settings.json.
  • A distinct, explicit action — e.g. /model save, --save flag, or a dedicated /settings set model command — should be required to persist a change.
  • When launching a new agent from the fleet view, the model and effort should be specifiable at launch, independently of any global default.
  • Each agent in the fleet view should maintain its own model/effort state for its lifetime.

Related issues (all closed as stale or duplicate without a fix)

  • #20745 — Model setting changes globally across all sessions (regression)
  • #27608 — Status bar shows stale model after settings.json change from another session (closed as duplicate)
  • #27627 — /model command is global, not per-session (closed as duplicate of #27608)
  • #36288 — /model affects all concurrent sessions instead of being per-session (closed as duplicate)
  • #37303 — effortLevel not persisted for Max mode; model/effort have no per-session isolation
  • #47820 — /effort session command does not override effortLevel in settings.json
  • #49076 — /model silently persists effortLevel to settings.json, overriding user's preferred default (closed as duplicate)
  • #49166 — /effort is global across concurrent sessions instead of session-scoped
  • #53416 — /effort setting is global across sessions instead of per-session (closed as duplicate of #49166)
  • #65598 — feat: support effort level in agent definition frontmatter (open — feature request for the same gap)
  • #66346 — effortLevel in settings.json not honoured on session start (open)

This issue is not a duplicate of any of the above. Those issues cover single sessions and concurrent terminal sessions. This issue covers the agents/fleet view, where the lack of per-agent launch configuration means there is no supported way to run agents with intentional, independent model and effort settings.

View original on GitHub ↗

9 Comments

xg-gh-25 · 2 months ago

We hit this exact issue running multi-agent fleets — every session writing to shared settings.json created a race condition where agent A's model choice would leak into agent B's next startup.

Our workaround: inject model+effort via frontmatter in the agent definition itself and defensively re-assert via /model on every session init. Not clean, but it prevents the shared-state churn.

The root issue is that /model and /effort look like imperative session commands but have declarative persistence semantics. You expect set model for THIS session, you get SAVE model as new default for ALL sessions.

A clean fix would be:

# Session-local (current behavior should be here)
/model sonnet    # affects only this session

# Persistent (requires explicit intent)  
/model save sonnet   # write to settings.json

The agents/fleet view amplifies this: you want agent A on Opus analyzing, agent B on Haiku summarizing, agent C on Sonnet routing — but the last /model call wins globally. There's no launch-time model override in the fleet UI, so you're stuck with CLI flags (which can't change mid-session) or living with the shared state churn.

Related: our self-healing hook pattern works around similar cross-session state issues by treating settings.json as append-only and using per-agent namespaces. But model/effort can't use that pattern since they're singletons.

---
We build multi-agent systems with SwarmAI. Discussion: T-CUL: Cultivation > Config

kcarriedo · 2 months ago

The root problem is that /model and /effort are presented as session controls but act as account-level settings writes. In a single-agent workflow that ambiguity is invisible. In a fleet it surfaces as a race condition the moment two agents are running concurrently — agent A picks /model and agent B picks it up on its next startup.

The settings.json injection workaround that xg-gh-25 describes above is the right short-term path: push model/effort into agent-definition frontmatter and treat settings.json as read-only at runtime. One addition worth trying: if you scope the frontmatter injection to a per-agent working directory (giving each agent an isolated ~/.claude/ equivalent), you also eliminate the restart-required discovery issue from #66327. The two issues share the same root: a single shared config file being used as both a persistent preference store and a runtime session signal.

The feature ask that would solve this cleanly: /model and /effort should write to a session-scoped config layer, not global settings.json. The global file becomes the default; session-level overrides live in memory or a session-specific file. That's the architecture that would make fleet management workable without workarounds.

asiz15 · 2 months ago

I routinely keep several Claude Code sessions open at once across different projects (e.g. a frontend and a backend repo), and I adjust effort dynamically within a single session depending on the task at hand: low/medium for a quick change, then high for a complex refactor a few minutes later. This is the core of how I work — effort is a per-task dial, not a per-launch decision.

The current behavior makes that impossible without cross-contamination:

Repro:

  1. Session A (frontend) and Session B (backend) both running, effort medium.
  2. In Session A I hit a complex task and run /effort high.
  3. Session B silently inherits high on its next turn — I never touched it.
  4. There's no warning and no way to know which session last mutated the shared settings.json.

CLI launch flags (--effort, CLAUDE_CODE_EFFORT_LEVEL) don't solve this, because they fix effort at startup — the whole point is changing it mid-session. The only workaround is an isolated CLAUDE_CONFIG_DIR per session, which also forks auth/MCP/hooks and is far too heavy for everyday use.

The root cause is the one already called out above: /model and /effort look like imperative session controls ("set effort for THIS session") but have declarative, account-global persistence semantics ("save as new default everywhere"). In single-session use the ambiguity is invisible; with any concurrency it's a correctness bug.

Proposed minimal fix — mirror the existing fastModePerSessionOptIn:

{ "effortLevelPerSessionOptIn": true }

When set, /effort (and ideally /model) apply to the current session only and do not write to settings.json. This already has precedent in the codebase, keeps the default behavior unchanged for those who rely on it, and fully fixes both the interactive multi-session and the fleet/agents cases.

Worth noting this is a regression (#56262): /effort used to be per-session and reset on next launch. Restoring that — even behind the opt-in flag above — would unblock a lot of daily power-user workflows.

jstm88 · 1 month ago

I discovered this when a complex session using Fable switched itself to Haiku and suddenly became very confused. 😆

I propose this to restore sane behavior:

  • Resumed sessions always resume with the same model/effort they were using previously, NOT from global/env. Only the --model/--effort flags can override this.
  • /model should NOT write to the global config. That command should be for changing the current model. Perhaps add an additional /model-default or a flag for setting it globally, as suggested by others.
  • Sessions should NOT spontaneously change the model when reloading the global config.
  • Resuming with a different model/effort (whether by CLI flag, or if the previous model is now unavailable) should ASK what to do - i.e. select the default, choose a different model, or cancel. That pattern is already in use (asking what to do if you resume a large session that will use a lot of tokens) so it already has established precedent.

It also seems that Claude itself has autonomously closed a bunch of these as duplicates, linking them to a stale one that had no actual discussion or action. Bad bot! Don't do that.

kevinmccann-td · 1 month ago

+1. Hit this today in a plain interactive session, not the fleet view. Ran /effort high for one heavy task and it silently saved as my default for new sessions. The only session-scoped path is startup flags, which can't help mid-session. Making /model and /effort session-only by default, with an explicit flag to persist, would cover both the single-session and fleet cases.

henricook · 1 month ago

Does this item from the change log for v2.1.211 partially fix this?

Fixed subagents spawned with an explicit model override reverting to the parent's model when resumed or sent a follow-up message"
jstm88 · 1 month ago
Does this item from the change log for v2.1.211 partially fix this? > Fixed subagents spawned with an explicit model override reverting to the parent's model when resumed or sent a follow-up message"

Without knowing the mechanism, that bug could have been related to this. If subagents were fixed without addressing the underlying issue, though, it may have just been patching a symptom without fixing the root cause.

The main issue is the leakage of state between sessions. And in the interest of saving myself a few minutes, here's Fable 5's own words. 😏

This is a classic scoping problem: a setting that feels session-local is actually global mutable state. It violates the principle of least surprise because the user's mental model ("I'm bumping up the model for this hard task") doesn't match the system's behavior ("you just changed the default for everything, everywhere, including sessions you can't see"). When the surprise has a direct cost dimension — an expensive model silently persisting into future work — the design flaw compounds into a financial footgun. The inverse is also bad: someone downgrades to a cheap model for a throwaway task and later gets degraded results without realizing why.

Relevant design principles it runs afoul of:

- Least surprise / least astonishment — an in-session command should have in-session effect unless the user explicitly asks otherwise. Global side effects from local actions are the canonical violation. - Scope clarity — good tools distinguish default (persistent config) from override (this session only). Conflating them removes the user's ability to express either intent. - Isolation — one session mutating another active session is shared mutable state with spooky action at a distance; sessions should be independent unless deliberately linked. - Safe defaults for costly actions — expensive options should require deliberate, visible opt-in, and shouldn't stick around implicitly. - Visibility of system state — if the setting does persist, the UI should say so ("model changed for all sessions") and show the active model prominently so drift is noticeable. The usual fix: session-level changes are ephemeral overrides, a separate explicit command (config set, a settings file) changes the persistent default, and already-running sessions never change out from under the user.

Its 5th point is addressed (the UI does at least briefly say it changed the default) but that's a weak assertion on top of a bad design. Fixing the design is the better choice.

kitaekatt · 1 month ago

Adding a version-control angle on the same root cause (/model writing to settings.json), distinct from the fleet-view cases above.

When settings.json is git-tracked — committed for a team, or synced across machines via a dotfiles/config repo — using /model the intended way (Enter in the picker, or /model <name>) writes the model field into that tracked file (per the v2.1.153 docs). So every routine model switch dirties a committed file: constant git status noise, spurious diffs, and merge conflicts when the same file is touched on two machines. Model choice is inherently per-user and often per-machine; persisting it into shared, tracked state couples a personal preference to team/repo state.

Suggestion: persist /model (and /effort) saved preferences to the gitignored settings.local.json (Local scope) rather than settings.json. Local already overrides Project and User in precedence, so the saved default still wins — without ever dirtying a tracked file. This dovetails with the "session-local by default + explicit save" asks above: whatever does get persisted should land in the Local scope.

Workaround today (imperfect): a git clean filter bound via .gitattributes that strips the model line from git's view of settings.json:

# .gitattributes
settings.json filter=claudemodel
# per clone (.git/config is not tracked, so each machine registers it):
git config filter.claudemodel.clean 'python3 strip-model.py'   # removes the "model" line from stdin
git config filter.claudemodel.smudge cat

This kills the churn — git status stays clean no matter what model the working tree holds — but it's per-clone setup, and any checkout that rewrites settings.json resets the on-disk model. That people reach for git filters to paper over a config write is itself a sign the persisted preference belongs in the gitignored Local scope.

sindrehenriksen · 1 month ago

No, I don't think v2.1.211 fixes this. That item is scoped to subagent model inheritance (a subagent losing its explicit override and falling back to the parent's model on resume), which is a different path from /model and /effort writing to global settings.json. Agreeing with @jstm88 that it's adjacent at best.

What's more telling is that the changelog already contains fixes circling this exact bug without changing the scoping:

  • v2.1.86 — "Fixed statusline showing another session's model when running multiple Claude Code instances and using /model in one of them". This is precisely the cross-session leak in this issue, and the fix was to correct the statusline display rather than to stop one session's /model from reaching another's state.
  • v2.1.162 — "/effort now confirms when your chosen level will persist as the default for new sessions". That's the visibility mitigation, i.e. a warning attached to the behavior rather than a scope fix.
  • v2.1.203 — "Fixed background sessions ignoring effortLevel changes in settings.json when forked through the daemon". This arguably tightens the coupling to global settings rather than loosening it.

Three separate fixes have touched the symptoms; none introduced a session-scoped override. The v2.1.86 one is the clearest: when the response to "one session's /model leaked into another" is to fix the statusline rendering, the write itself is being treated as correct behavior to render accurately rather than as the bug.

Confirming @kitaekatt's version-control observation first-hand, on v2.1.211: my ~/.claude/settings.json is symlinked from a tracked dotfiles repo, and it had no model field at all while I was on the default, so this stayed invisible. Selecting a model once via /model immediately wrote "model": "claude-fable-5[1m]" into the tracked file, and reserialization rewrote an adjacent unrelated line as well, so a routine model switch yields a multi-hunk diff in a committed file.

That said, I'd frame the fix differently. Persisting to settings.local.json instead would quiet the git noise, but it wouldn't address this issue: Local scope is still global across sessions, so one session's /model would go on changing every other session's default. It removes the diff, not the leakage. I track settings.json deliberately and am fine with a saved default living there.

The missing concept seems to be scope, not location. There are two genuinely distinct things a user wants, and today only one exists:

  1. A default — the model/effort new sessions start with. Persisting this is correct and desirable, wherever it's stored.
  2. A session-only change — "use a stronger model for this task, in this session, right now." This should not touch persisted state or any other session at all.

/model currently conflates them, so the only way to express (2) is to perform (1) and accept the side effects. A session-scoped default with an explicit opt-in to persist (/model --save, or a separate /model-default) would give both, and would make the fleet-view, cost, and version-control complaints in this thread fall out as consequences rather than needing individual patches.

One data point on observed behavior, since the reports here differ slightly. /model persists and applies to new sessions, but it does not appear to retroactively change sessions already open in other tabs. My tentative read is that other sessions pick up the change when they reload rather than while live, which would be consistent with @jstm88's session that switched to Haiku on reload. I haven't tested rigorously, so treat it as a hint: worth pinning down whether the leak happens at reload specifically, since that affects where a fix needs to sit.