[TUI 0.145.0] Esc-Esc Edit/Resume changes gpt-5.6-sol Ultra to xhigh

Open 💬 5 comments Opened Jul 25, 2026 by git-longcat

What version of Codex CLI is running?

codex-cli 0.145.0

What subscription do you have?

ChatGPT Pro 5x

Which model were you using?

gpt-5.6-sol

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What terminal emulator and version are you using (if applicable)?

Windows Command Prompt

Codex doctor report

Not collected. The issue reproduces deterministically in a short, otherwise normal TUI session.

What issue are you seeing?

After selecting gpt-5.6-sol with Ultra reasoning, using the Esc-Esc
Edit/Resume flow silently changes the reasoning effort back to xhigh.

Ultra remains selected during normal conversation turns. The change occurs
immediately after pressing Enter to confirm a previous prompt, before the
restored prompt is submitted again.

What steps can reproduce the bug?

  1. Start Codex CLI 0.145.0.

The TUI initially displays gpt-5.6-sol xhigh.

  1. Run /model, select gpt-5.6-sol, and select Ultra.

The TUI now displays gpt-5.6-sol ultra.

  1. Send 1+1=?.

Codex answers 2, and the TUI still displays gpt-5.6-sol ultra.

  1. Send 2+2=?.

Codex answers 4, and the TUI still displays gpt-5.6-sol ultra.

  1. Press Esc twice.
  1. The previous prompt, 2+2=?, is highlighted. Press Enter to select it

for Edit/Resume.

  1. Before submitting the restored prompt, observe that the model indicator

has changed to gpt-5.6-sol xhigh.

What is the expected behavior?

The prompt-edit branch should preserve the active conversation's selected
model and reasoning effort, so it should remain gpt-5.6-sol ultra.

The reasoning effort should not silently fall back to the configured
non-Ultra default when creating a branch from an earlier prompt. If this
reset is intentional, the TUI should clearly disclose it before creating
the branch.

Additional information

  • The issue reproduces with one Esc-Esc-Enter gesture; repeated or rapid

input is not required.

  • No reasoning-effort shortcut keys were used.
  • Normal conversation turns preserve Ultra.
  • The visible change occurs during prompt-edit branch creation or attachment,

not after a new model response.

  • The observed change is confirmed by the TUI model indicator. The next

request payload was not independently inspected.

  • A possible cause is that the prompt-edit fork path uses the configured

default effort (xhigh) rather than copying the active conversation's
current effort (Ultra).

Related issues

  • #35286 involves the same Esc-Esc-Enter prompt-edit fork path, but reports

buffered input creating multiple chained forks. This issue reproduces with
a single gesture and concerns the selected reasoning effort not being
preserved.

  • #35003 involves the same earlier-prompt fork feature in Codex CLI 0.145.0,

but reports copied record timestamps being rewritten. It does not report
a model or reasoning-effort change.

  • #11013 and #12042 report reasoning effort changing when entering Plan mode.

This reproduction does not enter Plan mode; it is triggered by confirming
an Esc-Esc Edit/Resume selection.

  • #26930 reports reasoning effort changing in Codex Desktop during

delegation or continuation. This report is a deterministic Codex CLI TUI
reproduction involving a prompt-edit branch.

View original on GitHub ↗

5 Comments

ysl2 · 1 month ago

Download this file and run: git apply prompt-edit-preserve-reasoning-effort.patch
prompt-edit-preserve-reasoning-effort.patch

The patch file's content is as below:

diff --git a/codex-rs/tui/src/app/event_dispatch.rs b/codex-rs/tui/src/app/event_dispatch.rs
index b8b1bce2b4..e9e9f03032 100644
--- a/codex-rs/tui/src/app/event_dispatch.rs
+++ b/codex-rs/tui/src/app/event_dispatch.rs
@@ -246,7 +246,8 @@ impl App {
                 );
                 self.refresh_in_memory_config_from_disk_best_effort("forking the thread")
                     .await;
-                let config = self.fresh_session_config();
+                let mut config = self.fresh_session_config();
+                config.model_reasoning_effort = self.chat_widget.current_reasoning_effort();
                 let started = match app_server
                     .thread_read(thread_id, /*include_turns*/ true)
                     .await
diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs
index e27e82522d..ade045fe1e 100644
--- a/codex-rs/tui/src/app/tests.rs
+++ b/codex-rs/tui/src/app/tests.rs
@@ -6410,6 +6410,8 @@ async fn prompt_edit_forks_before_selected_prompt_and_preserves_source() -> Resu
         .await?;
     app.enqueue_primary_thread_session(started.session, started.turns)
         .await?;
+    app.chat_widget
+        .set_reasoning_effort(Some(ReasoningEffortConfig::Ultra));
     while app_event_rx.try_recv().is_ok() {}
     let source_before = std::fs::read_to_string(&source_path)?;
     let mut tui = crate::tui::test_support::make_test_tui()?;
@@ -6441,6 +6443,22 @@ async fn prompt_edit_forks_before_selected_prompt_and_preserves_source() -> Resu
         .thread_id()
         .expect("prompt edit should switch to a forked thread");
     assert_ne!(forked_thread_id, source_thread_id);
+    assert_eq!(
+        app.chat_widget.current_reasoning_effort(),
+        Some(ReasoningEffortConfig::Ultra)
+    );
+    let rendered = app
+        .clear_ui_header_lines_with_version(/*width*/ 80, "<VERSION>")
+        .iter()
+        .map(|line| {
+            line.spans
+                .iter()
+                .map(|span| span.content.as_ref())
+                .collect::<String>()
+        })
+        .collect::<Vec<_>>()
+        .join("\n");
+    assert_app_snapshot!("prompt_edit_preserves_ultra_reasoning", rendered);
     assert_eq!(app.chat_widget.composer_text_with_pending(), prompt.text);
     assert_eq!(
         app.chat_widget.remote_image_urls(),
diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__tests__prompt_edit_preserves_ultra_reasoning.snap b/codex-rs/tui/src/snapshots/codex_tui__app__tests__prompt_edit_preserves_ultra_reasoning.snap
new file mode 100644
index 0000000000..adaff9c5e6
--- /dev/null
+++ b/codex-rs/tui/src/snapshots/codex_tui__app__tests__prompt_edit_preserves_ultra_reasoning.snap
@@ -0,0 +1,10 @@
+---
+source: tui/src/app/tests.rs
+expression: rendered
+---
+╭─────────────────────────────────────────────────╮
+│ >_ OpenAI Codex (v<VERSION>)                    │
+│                                                 │
+│ model:     gpt-5.6-sol ultra   /model to change │
+│ directory: /tmp/project                         │
+╰─────────────────────────────────────────────────╯
bbingz · 1 month ago

Analysis (community)

Residual analysis for #35292 (TUI Esc-Esc Edit/Resume drops gpt-5.6-sol Ultraxhigh) under docs/contributing.md — invitation-only. No unsolicited PR.

Root-cause hypothesis

This is not a hardcoded Ultra → XHigh rewrite. Applying advanced Ultra deliberately splits state (live chat = Ultra, durable config.model_reasoning_effort = non-Ultra default). Esc-Esc PromptEdit then forks with fresh_session_config() without copying the live current_reasoning_effort(), so the fork override carries the stale non-Ultra value (often the session’s prior xhigh). Ordinary /fork copies live effort and does not show this bug.

Verified at HEAD 4c43465133:

1. Ultra apply splits live vs config.
codex-rs/tui/src/app/config_persistence.rs on_apply_advanced_reasoning:

  • Sets the chat widget to the chosen advanced effort (Ultra).
  • Sets self.config.model_reasoning_effort to default_reasoning_effort_for_conversation_model(model) — which filters out Ultra and prefers a supported configured non-Ultra effort, then model default, then first non-Ultra supported option.

Unit coverage at :1207-1238 shows a live Ultra session coexisting with a non-Ultra fresh_session_config().

2. PromptEdit path omits live effort (bug pin).
codex-rs/tui/src/app/event_dispatch.rs ForkSessionForPromptEdit:

  • refresh_in_memory_config_from_disk_best_effort
  • let config = self.fresh_session_config();no assignment of chat_widget.current_reasoning_effort()
  • fork_thread_at(config, …)

Contrast ordinary /fork in the same file (:175-184): it does set
fork_config.model_reasoning_effort = self.chat_widget.current_reasoning_effort()
before fork_thread. The PromptEdit omission is path-specific, not a general fork policy.

3. Override is transmitted and applied server-side.
TUI packs config["model_reasoning_effort"] into ThreadForkParams (app_server_session.rs fork params region; unit test confirms lifecycle params forward overrides).
App-server fork path loads overrides via load_for_cwd(request_overrides, …) (thread_processor.rs ~4080–4168) and returns session_configured.reasoning_effort from the new config — it does not re-read the source thread’s Ultra. The TUI then refreshes display from that returned effort (session_flow / PromptEdit attach).

4. Why the issue sees exactly xhigh.
Reporter starts at xhigh, selects Ultra, one Esc-Esc-Enter shows xhigh before resubmit. At HEAD, gpt-5.6-sol supports both xhigh and Ultra (models-manager/models.json). Because default_reasoning_effort_for_conversation_model reuses the configured non-Ultra effort when still supported, a prior xhigh config is the deterministic fallback — not a magic Ultra→XHigh table.

5. Scope honesty.

  • Plain resume is a separate path (not fully closed as identical).
  • No Windows TUI E2E packet capture in this pass; the chain is closed in OSS unit-tested pieces + control flow.
  • Residual is: PromptEdit fork must preserve live advanced effort (or re-prompt advanced confirmation), matching /fork.

Ranked fix outline (all OSS):

  1. In ForkSessionForPromptEdit, after fresh_session_config(), set

config.model_reasoning_effort = self.chat_widget.current_reasoning_effort()
(mirror /fork).

  1. Or: stop writing non-Ultra into self.config on advanced apply; keep durable config aligned with live effort when user explicitly chose Ultra.
  2. Or: if live effort is advanced (Ultra/Max), force the advanced picker confirmation on PromptEdit re-submit.
  3. Regression test: live Ultra + PromptEdit fork params contain model_reasoning_effort: ultra (today: non-Ultra / xhigh).

Fail-first tests already nearby: conversation_reasoning_uses_compatible_default_for_new_threads, thread_lifecycle_params_forward_config_overrides_and_service_tier — add a PromptEdit-specific case that asserts live Ultra survives.

Happy to draft the one-line PromptEdit fix + test if maintainers want it. No unsolicited PR.

git-longcat · 19 days ago

I tested CLI 0.147.0. When #37421 occurs, Ultra remains selected only because the backtrack fork fails: the session ID does not change, later turns remain, and the edited prompt is appended to the original thread. Therefore, #37421 masks #35292 but is not a usable workaround.

bbingz · 19 days ago

Thanks @git-longcat — that lines up with the residual pin.

When the Esc-Esc backtrack fork fails (#37421), live Ultra is never rewritten through ForkSessionForPromptEdit / fresh_session_config(), so the UI can still show Ultra only because the bad fork path did not run. That masks #35292; it is not a fix or usable workaround.

Once fork succeeds again, expect the live Ultra → durable non-Ultra (often prior xhigh) drop until PromptEdit copies current_reasoning_effort() the same way ordinary /fork already does.

zzh-www · 2 days ago

Additional reproduction: prompt edit can inherit another session's default model

I confirmed that the same prompt-edit fork path can also replace the active conversation model, not only its reasoning effort.

Reproduction
  1. Start a conversation and select current-session-model with High reasoning.
  2. Let another conversation (or a direct config edit) persist other-session-default-model with Low reasoning to config.toml.
  3. Return to the first conversation and use Esc-Esc prompt edit on an earlier message.
  4. The newly forked conversation inherits the persisted default model/effort instead of the active conversation model/effort.

This is especially surprising because switching models in an unrelated session can update the default configuration and therefore alter a later prompt-edit fork in the original session.

Root cause

ForkSessionForPromptEdit refreshes config from disk and then uses fresh_session_config() without restoring the live chat widget model or reasoning effort. The normal /fork path already copies both live values.

Tested fix

After creating the fresh config, copy both active values before calling the fork path:

let mut config = self.fresh_session_config();
config.model = Some(self.chat_widget.current_model().to_string());
config.model_reasoning_effort = self.chat_widget.current_reasoning_effort();

The regression test sets one live model/effort, writes different defaults to config.toml, triggers prompt edit, and asserts that the fork retains the live values while preserving the existing source-history behavior.

Patch commit: https://github.com/zzh-www/codex/commit/af773ce736aa2995598bf48d1a63e00547a09830

Local validation:

  • The prompt-edit regression test passes.
  • just fix -p codex-tui passes.
  • The crate-wide TUI run completed 3,811 tests successfully and reported three unrelated snapshot mismatches in patch approval/footer rendering.