Slash-command settings should be session-scoped by default
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?
- Start Codex without an explicit service-tier override.
- Use
/fast, intending it as a one-off current-thread change. - Observe that
~/.codex/config.tomlgainsservice_tier = "fast". - Start a fresh, unrelated Codex process without a service-tier argument.
- 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.
1 Comment
@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 throughset_service_tier_selection()incodex-rs/tui/src/chatwidget/service_tiers.rs. It does two things back-to-back, unconditionally:AppCommand::override_turn_context(session-scoped, this part is correct and matches the intended behavior).AppEvent::PersistServiceTierSelection, which is handled incodex-rs/tui/src/app/event_dispatch.rs(~line 2111) and writes straight toconfig.tomlviabuild_service_tier_selection_edits()incodex-rs/tui/src/config_update.rs(~line 91).There's no gate between these two steps: no confirmation, no opt-in. So every
/fasttoggle persists globally, even though the docs (and presumably the intent) suggest persistence should be a deliberate second action.I checked
/modeland/personality(model_popups.rs,settings_popups.rs) and they follow the identical shape:Update*event (session-scoped, correct) is immediately followed by an unconditionalPersist*Selectionevent (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 aSelectionActionpopup (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//personalityapplies 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 existingPersist*Selectionevent, 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
/fastonly (smallest, matches the original repro exactly), gate its persist event behind the popup above, and leave/model//personalityas 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.