Slash-command settings should be session-scoped by default

Open 💬 1 comment Opened Aug 19, 2026 by hogjosh

What version of Codex are you using?

Codex CLI 0.148.0

What platform is your computer?

macOS arm64

What issue are you seeing?

Manual slash-command settings are unexpectedly sticky across unrelated sessions.

I used /fast as a one-off change for the current thread. Codex wrote service_tier = "fast" to ~/.codex/config.toml, so later fresh Codex processes and unrelated agents inherited the accelerated tier even though their launch commands did not request it.

The broader problem is not limited to /fast: most or all interactive slash-command selectors—such as model, reasoning effort, personality, and service tier—should be scoped to the current thread/session by default. A manual experiment in one conversation should not silently change defaults for future conversations.

What steps can reproduce the bug?

  1. Start Codex without an explicit service-tier override.
  2. Use /fast, intending it as a one-off current-thread change.
  3. Observe that ~/.codex/config.toml gains service_tier = "fast".
  4. Start a fresh, unrelated Codex process without a service-tier argument.
  5. Observe that the new process inherits Fast.

What is the expected behavior?

Slash-command changes should affect only the current thread/session by default.

Persisting a choice for future sessions should require a separate, explicit action such as Make default, or a clearly labeled confirmation. The UI should distinguish:

  • the effective value for the current thread;
  • the configured default for future threads; and
  • whether the current action will mutate persistent configuration.

This should apply consistently to /fast, /model, reasoning-effort, personality, and similar interactive selectors unless a setting is inherently global and clearly presented as such.

Why this matters

The current behavior makes one-off experimentation unsafe and can unintentionally change cost, latency, or behavior across unrelated agents and workstreams.

A command-line override such as -c service_tier=default is a workaround, but callers should not need to defensively override a persistent mutation they never intentionally made.

Additional information

Feedback was also submitted through /feedback without logs. Feedback thread ID: 01a01bb2-7780-7232-ba37-8d749e19ec8b.

View original on GitHub ↗

1 Comment

csarushan1729 · 8 days ago

@hogjosh thanks for the clear repro. I dug into this a bit. Here's what I found tracing through the source, in case it's useful.

Root cause

/fast (and /model, /personality - same pattern, see below) go through set_service_tier_selection() in codex-rs/tui/src/chatwidget/service_tiers.rs. It does two things back-to-back, unconditionally:

  1. Applies the change to the current thread via AppCommand::override_turn_context (session-scoped, this part is correct and matches the intended behavior).
  2. Immediately also sends AppEvent::PersistServiceTierSelection, which is handled in codex-rs/tui/src/app/event_dispatch.rs (~line 2111) and writes straight to config.toml via build_service_tier_selection_edits() in codex-rs/tui/src/config_update.rs (~line 91).

There's no gate between these two steps: no confirmation, no opt-in. So every /fast toggle persists globally, even though the docs (and presumably the intent) suggest persistence should be a deliberate second action.

I checked /model and /personality (model_popups.rs, settings_popups.rs) and they follow the identical shape: Update* event (session-scoped, correct) is immediately followed by an unconditional Persist*Selection event (writes to disk, no gate). So this issue isn't specific to /fast; it's systemic to all three interactive selectors mentioned in the original report.

A pattern that's already in the codebase for this exact problem

open_plan_reasoning_scope_prompt (model_popups.rs) already solves a related "which scope should this apply to" question via a SelectionAction popup (self.bottom_pane.show_selection_view(...)); it lets the user choose between two named actions after making a selection. That's a ready-made mechanism for exactly this: after /fast//model//personality applies a change to the current session, show a short prompt with two options: "Use for this session only" (no-op, already applied) vs. "Save as default" (fires the existing Persist*Selection event, unchanged). No new UI primitive needed, just reusing what's already there.

Suggested scope for a first pass

Given the "why we don't accept unsolicited PRs" note in CONTRIBUTING, happy to just leave this as analysis, but if it's useful: I'd scope an initial fix to /fast only (smallest, matches the original repro exactly), gate its persist event behind the popup above, and leave /model//personality as an explicit follow-up once there's agreement the popup UX is the right call rather than changing all three at once.

Happy to go deeper on any part of this if it's helpful.