Interrupt can merge queued follow-up input with later composer text into a single next-turn message

Open 💬 2 comments Opened Jun 23, 2026 by starSumi

Summary

Queued follow-up input can be merged with later composer text after an interrupt, and the next turn is then persisted as a single user message.

I am not reporting only a historical log reconstruction here. I reproduced this locally against codex-cli 0.142.0 on Windows 11 using a live interactive codex session, and then verified the resulting history and rollout files on disk.

The confusing part is that two distinct user intents become one next-turn message:

  1. a follow-up that was explicitly queued while a turn was running
  2. additional text typed later into the composer before interrupting that running turn

After Esc interrupts the running turn, pressing Enter submits both as one merged message.

Environment

  • Codex CLI: 0.142.0
  • Platform: Windows 11
  • Shell host: PowerShell 7 terminal environment
  • Working directory during repro: E:\Zero_Base\sumi_os
  • Reproduced on: June 23, 2026

Reproduction

I was able to reproduce this with a live interactive session:

  1. Launch codex --no-alt-screen.
  2. Submit a long-running prompt, for example a prompt that makes Codex run:
  • pwsh -NoProfile -Command Start-Sleep -Seconds 60
  1. While that turn is still running, type a follow-up marker and press Tab so it becomes queued.
  2. Without submitting, type additional text into the composer.
  3. Press Esc to interrupt the running turn.
  4. Press Enter once after the interrupt restore.

Actual Behavior

The queued follow-up and the later composer text are merged into a single next-turn user message.

In my live repro, the next persisted user message became:

FOLLOWUP_mixq_20260623_1
 IMPL_mixq_20260623_1

This was persisted as one message in both local history and rollout state, even though those two pieces of text were entered in different phases:

  • the first line was explicitly queued during the running turn
  • the second line was typed later into the composer before the interrupt-driven resubmission

Expected Behavior

One of these should happen instead:

  • queued follow-up input and later post-interrupt composer text should remain distinct
  • the UI should explicitly show that they are about to be merged before submission
  • persisted telemetry should record enough queue / restore metadata to make the merge unambiguous during later debugging

Why This Looks Real Rather Than User Confusion

The persisted sequence in the live repro matched the behavior I saw:

  1. the first turn started
  2. the first user message was persisted
  3. the turn was persisted as turn_aborted with reason interrupted
  4. the next turn started
  5. the next user message was persisted as the merged follow-up plus later draft

So this is not just a UI impression. The merged next-turn message shows up in durable local history.

Relevant Source Paths

The current local source also appears consistent with the observed behavior:

  • tui/src/chatwidget/input_flow.rs
  • running or not-yet-configured input is queued in queued_user_messages
  • tui/src/chatwidget/input_restore.rs
  • on_interrupted_turn() restores queued messages into the composer
  • queued messages are merged with the current composer state instead of being kept as a separate submission boundary

There are already tests around the individual pieces of this behavior, for example:

  • startup queued input submission
  • interrupt restoring queued messages into the composer
  • interrupt prepending queued messages before existing composer text

The issue is the combined outcome: distinct user intents can collapse into one next-turn message.

Notes

  • I also hit a local rusty_v8 Windows symlink privilege problem when trying to run the Rust test target directly, so my strongest evidence here is the live interactive repro plus the persisted rollout/history verification.
  • I think there may be two related concerns:
  • the product behavior itself
  • observability, because the persisted rollout does not seem to expose a first-class queued_input marker that explains this merge directly

View original on GitHub ↗

2 Comments

starSumi · 2 months ago

I pushed a candidate fix branch for this issue:

The fix keeps a newer in-progress composer draft in place when Esc interrupts a turn that already has explicit queued follow-up input, and lets that queued follow-up start the next turn directly instead of merging both intents into one restored message.

Validation run locally:

  • cargo fmt --all
  • git diff --check
  • attempted cargo test -p codex-tui interrupt_starts_next_queued_turn_and_preserves_existing_composer_metadata -- --exact

I also resolved the earlier local Windows symlink privilege blocker by enabling Developer Mode, but the clean-branch targeted test still did not finish within my local execution window, so I am not claiming a full test pass here.

I tried to open a PR from the fork, but GitHub returned:

GraphQL: starSumi does not have the correct permissions to execute CreatePullRequest (createPullRequest)

starSumi · 2 months ago

I refreshed this against current main and pushed a current-main candidate branch:

The old branch I mentioned earlier is now stale against main, so I re-applied the idea on a clean worktree. The current-main patch keeps the existing restore behavior for non-manual-interrupt cases, but changes the Esc interrupt path when both of these are true:

  1. there is already explicit queued follow-up input, and
  2. the composer contains a newer in-progress draft.

In that case, the queued follow-up starts the next turn and the newer draft remains in the composer, instead of collapsing both user intents into one restored draft.

Validation I was able to run locally:

  • cargo fmt --all -- --check passed
  • git diff --check passed

I also attempted the focused test:

  • cargo test -p codex-tui interrupt_starts_next_queued_turn_and_preserves_existing_composer_metadata -- --exact

but I am not claiming a test pass: on this Windows machine the Rust build did not complete within my local execution window after dependency compilation/resource issues. I am sharing this as current-main analysis and a concrete fix direction, not as a merge-ready PR.

I also noticed docs/contributing.md now says external PRs are by invitation only, so I am not opening another PR unless a maintainer explicitly asks for one. The branch is available if it is useful for maintainers to inspect or cherry-pick.