TUI: Quickly interrupting a submitted prompt drops it from rollout history

Open 💬 3 comments Opened Aug 13, 2026 by zengjian513f
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.147.0 (reported as current/latest by codex doctor --json)

What subscription do you have?

ChatGPT authentication (the exact subscription tier is not exposed by the CLI)

Which model were you using?

gpt-5.6-sol, max reasoning, fast mode

What platform is your computer?

Linux 7.0.0-28-generic x86_64 x86_64

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

xterm.js in a browser, attached through tmux 3.4 (TERM=xterm-256color)

Codex doctor report

{
  "codexVersion": "0.147.0",
  "overallStatus": "warning",
  "authMode": "chatgpt",
  "model": "gpt-5.6-sol",
  "modelProvider": "openai",
  "wireApi": "responses",
  "websocketReachability": "ok",
  "platform": "linux-x86_64",
  "installation": "standalone",
  "terminal": "xterm-256color",
  "multiplexer": "tmux 3.4",
  "latestVersionStatus": "current version is not older"
}

The unrelated local paths and state inventory from the full doctor report are omitted.

What issue are you seeing?

If I submit a prompt in the interactive TUI with Enter and then press Escape soon after the turn starts, the TUI visibly echoes the submitted prompt and reports an interrupted turn, but the actual user prompt can be permanently absent from the rollout JSONL.

In one measured reproduction, the rollout contained:

15:24:09.897Z  event_msg: task_started
15:24:10.773Z  event_msg: turn_aborted (duration_ms: 865)

Between those events Codex wrote injected developer/user context, but it never wrote a response_item containing the prompt that I had submitted. The terminal scrollback still showed that prompt.

I then submitted a second prompt and received a normal assistant reply. The second prompt and reply were appended to the same rollout, but the first interrupted prompt was not backfilled. Resuming or independently reconstructing the session therefore cannot recover it.

This creates an ambiguous user contract: the TUI showed that the prompt was accepted and the turn started, but durable history behaves as if the prompt never existed. A follow-up such as “ignore my previous instruction” can now refer to the wrong persisted message. It is also unclear to the user whether the live model saw the interrupted prompt.

What steps can reproduce the bug?

  1. Start a new interactive Codex CLI session.
  2. Type a unique prompt and press Enter.
  3. As soon as the TUI enters the working state, press Escape to interrupt it.
  4. Observe that the submitted prompt remains visible in terminal scrollback and the turn is marked interrupted.
  5. Submit a second prompt and wait for a normal assistant reply.
  6. Inspect the session rollout under ~/.codex/sessions/.../*.jsonl, or resume the session.
  7. The second prompt and response are present, while the first prompt may be completely absent. The later turn does not backfill it.

The issue appears timing/phase dependent: other turns interrupted several seconds later already had their user message persisted. There does not appear to be a reliable user-visible boundary indicating when cancellation is safe.

What is the expected behavior?

Submitting a prompt and recording it in conversation history should have consistent semantics. Preferably the user message should be durably recorded before task_started, followed by a structured aborted status if Escape interrupts the turn.

Alternatively, if Escape occurs before submission is committed, the TUI should restore the text to the composer or explicitly mark it as “not submitted.” It should not both display the prompt as accepted and silently omit it from durable history.

Additional information

  • Reproduced in a newly created CLI session.
  • The missing prompt was not restored by a subsequent successful turn.
  • The actual thread ID and unredacted local rollout are available privately if a maintainer needs them.
  • This differs from #31239: that issue records thread_rolled_back and hides an older message that remains physically present. Here the newly submitted prompt never appears in the rollout at all.

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 14 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #38210

Powered by Codex Action

shleder · 14 days ago

I’m testing Codex Rescue against real interrupted-session boundaries. This case is especially useful because the submitted prompt appears to have never become durable rollout data.

Rescue cannot recreate a prompt that was never persisted, and I don’t want to imply otherwise. What I’d like to validate is whether it diagnoses the interrupted boundary conservatively instead of fabricating missing history.

If you still have the reproduction, would you be willing to run:

pipx install codex-rescue==0.1.0a3
codex-rescue doctor --latest

Sanitized output is enough; no raw rollout, prompt text, DBs, credentials, or private paths needed.

https://github.com/shleder/codex-rescue

jdcodes1 · 10 days ago

Consistent mechanism on main @ 1f41cc5d92: the user prompt is persisted by run_hooks_and_record_inputs at turn start (core/src/session/turn.rs#L246, impl #L597-L624), and that call sits after cancellable awaits — each input first runs inspect_pending_input (pre-input hooks), and the turn-start path also awaits MCP refresh/context assembly before recording. An Esc-abort cancels the whole turn future, so if it lands inside that window, record_pending_input never executes: the TUI already echoed the prompt optimistically, injected context got written by earlier stages, but the prompt item itself is gone — matching your 865 ms task_started → turn_aborted trace exactly.

Fixes:

  1. Persist the user input as the first durable action of the turn (or shield the record step from cancellation via a spawned task), so echo-in-UI implies present-in-rollout.
  2. On abort before the model saw the prompt, restore it to the composer (the interrupt-restore machinery in chatwidget/input_restore.rs already does this for other abort paths) — which also answers your "did the model see it?" ambiguity: restored = no.