openai / openai/codex

TUI: queued follow-up inputs are permanently stranded after a failed prompt-edit branch

Open
#37,974 3 comments 13 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI session TUI
Dominant language
Rust
Stars
125k
Forks
19.5k
PR merge metrics
PR metrics pending

Description

What version of Codex CLI is running?

codex-cli 0.147.0

What platform is your computer?

Linux 5.15.120 x86_64 (also reproducible on other platforms — the bug is in the TUI prompt-edit/backtrack logic, not platform-specific)

What issue are you seeing?

When editing an earlier prompt (Esc-Esc backtrack → select a prompt → Enter), the TUI forks the thread before the selected turn. If that fork fails — e.g. the composer shows:

■ Failed to branch before the selected prompt: the selected prompt was not found in the persisted thread

— then any user inputs that were queued while the fork was in flight get permanently stranded. They stay in "Queued follow-up inputs" forever and are never submitted, even after the composer returns to the Ready state. The queue just keeps growing with every new message and nothing is ever sent to the model.

Note: the "not found in the persisted thread" guard itself is legitimate defensive behavior (the visible transcript projection can't always be matched back to a persisted turn — e.g. after history pagination, or when large context blocks are injected as user messages by a wrapper). The bug is purely in the failure-handling path, which forgets to re-drive the input queue.

Root cause

Every successful fork path re-drives the follow-up input queue via maybe_send_next_queued_input():

  • the sibling AppEvent::ForkSession handler (codex-rs/tui/src/app/event_dispatch.rs),
  • the startup path (handle_startup_thread_started),
  • the thread-start path.

But the branch-failure path does not. In codex-rs/tui/src/app_backtrack.rs:

pub(crate) fn restore_backtrack_prompt_after_branch_error(
    &mut self,
    prompt: UserMessage,
    err: impl std::fmt::Display,
) {
    self.chat_widget.restore_user_message_to_composer(prompt);
    self.chat_widget.add_error_message(format!(
        "Failed to branch before the selected prompt: {err}"
    ));
    // <-- missing: maybe_send_next_queued_input(); the current thread is still live,
    //     but queued follow-ups are never re-driven, so they stay stuck forever.
}

Since the fork failed, the original thread stays live, but the queue is never flushed, so queued inputs are stranded.

What steps can reproduce the bug?
  1. In a live thread, enqueue one or more follow-up inputs (e.g. queue messages while a turn is running, or via a wrapper that injects inputs).
  2. Trigger prompt editing (Esc-Esc backtrack) and select a prompt whose fork will fail — the reliable way to force the failure is any state where the selected transcript prompt can't be resolved back to a persisted turn (the selected prompt was not found in the persisted thread).
  3. Observe that after the error, the composer returns to Ready, but the queued follow-up inputs remain in "Queued follow-up inputs" and are never submitted.
What is the expected behavior?

After a failed prompt-edit branch, since the current thread is still live, queued follow-up inputs should be submitted once the composer is idle — exactly like every successful fork path already does.

Additional information

I have a minimal fix and regression test ready (one-line fix + a test asserting the queued input is submitted after a failed prompt-edit branch, verified to fail without the fix and pass with it). openai/codex does not appear to accept PRs from external forks, so I'm filing this issue instead. Happy to share the patch/branch if useful:

    pub(crate) fn restore_backtrack_prompt_after_branch_error(
        &mut self,
        prompt: UserMessage,
        err: impl std::fmt::Display,
    ) {
        self.chat_widget.restore_user_message_to_composer(prompt);
        self.chat_widget.add_error_message(format!(
            "Failed to branch before the selected prompt: {err}"
        ));
        // The prompt edit never took effect, so the current thread stays live. Follow-up inputs
        // queued while the fork was in flight would otherwise remain stuck, because unlike the
        // successful fork paths this branch never re-drives the queue. Flush it here so queued
        // messages are submitted once the composer is idle again.
        self.chat_widget.maybe_send_next_queued_input();
    }

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in codex-rs/tui/src/app_backtrack.rs at restore_backtrack_prompt_after_branch_error, then compare the successful fork handlers in codex-rs/tui/src/app/event_dispatch.rs and the startup and thread-start paths. Run the relevant TUI regression tests, or add coverage for a failed prompt-edit branch with queued inputs. Done means the queued input is submitted after the composer returns to an idle state.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.