codex - 💡(How to fix) Fix Codex Desktop editing a queued message should preserve its queue position

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

  • git diff --check passed.
  • cargo fmt ran, with warnings about imports_granularity = Item requiring nightly.
  • cargo test -p codex-tui edited_queued_message_requeues_at_original_position was blocked before compilation because the local Homebrew Rust toolchain is rustc 1.89.0, while current dependencies require Rust 1.91.x.

Code Example

diff --git a/codex-rs/tui/src/chatwidget/user_messages.rs b/codex-rs/tui/src/chatwidget/user_messages.rs
@@
 pub(super) struct QueuedUserMessage {
     pub(super) user_message: UserMessage,
     pub(super) action: QueuedInputAction,
 }
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) struct QueuedUserMessageEdit {
+    pub(super) index: usize,
+}
@@
 pub(crate) struct ThreadInputState {
     pub(super) queued_user_messages: VecDeque<QueuedUserMessage>,
     pub(super) queued_user_message_history_records: VecDeque<UserMessageHistoryRecord>,
+    pub(super) active_queued_user_message_edit: Option<QueuedUserMessageEdit>,
     pub(super) user_turn_pending_start: bool,
 }
diff --git a/codex-rs/tui/src/chatwidget/input_queue.rs b/codex-rs/tui/src/chatwidget/input_queue.rs
@@
 use super::QueuedUserMessage;
+use super::QueuedUserMessageEdit;
 use super::UserMessage;
 use super::UserMessageHistoryRecord;
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) enum QueuedUserMessageInsert {
+    Front,
+    Back,
+}
+
 pub(super) struct InputQueueState {
     pub(super) queued_user_messages: VecDeque<QueuedUserMessage>,
+    pub(super) active_queued_user_message_edit: Option<QueuedUserMessageEdit>,
     pub(super) queued_user_message_history_records: VecDeque<UserMessageHistoryRecord>,
 }
@@
     pub(super) fn clear(&mut self) {
         self.queued_user_messages.clear();
+        self.active_queued_user_message_edit = None;
         self.queued_user_message_history_records.clear();
     }
+
+    pub(super) fn insert_queued_user_message(
+        &mut self,
+        user_message: UserMessage,
+        action: crate::bottom_pane::QueuedInputAction,
+        history_record: UserMessageHistoryRecord,
+        fallback: QueuedUserMessageInsert,
+    ) {
+        let queued_message = QueuedUserMessage::new(user_message, action);
+        if let Some(edit) = self.active_queued_user_message_edit.take() {
+            let index = edit.index.min(self.queued_user_messages.len());
+            self.queued_user_messages.insert(index, queued_message);
+            self.queued_user_message_history_records.insert(index, history_record);
+        } else if fallback == QueuedUserMessageInsert::Back {
+            self.queued_user_messages.push_back(queued_message);
+            self.queued_user_message_history_records.push_back(history_record);
+        } else {
+            self.queued_user_messages.push_front(queued_message);
+            self.queued_user_message_history_records.push_front(history_record);
+        }
+    }
 }
diff --git a/codex-rs/tui/src/chatwidget/input_restore.rs b/codex-rs/tui/src/chatwidget/input_restore.rs
@@
     pub(super) fn pop_latest_queued_user_message(&mut self) -> Option<UserMessage> {
         if let Some(user_message) = self.input_queue.queued_user_messages.pop_back() {
+            self.input_queue.active_queued_user_message_edit = Some(QueuedUserMessageEdit {
+                index: self.input_queue.queued_user_messages.len(),
+            });
             let history_record = self
                 .input_queue
                 .queued_user_message_history_records
@@
         } else {
+            self.input_queue.active_queued_user_message_edit = None;
             let user_message = self.input_queue.rejected_steers_queue.pop_back()?;
         }
     }
@@
             queued_user_messages: self.input_queue.queued_user_messages.clone(),
             queued_user_message_history_records: self.input_queue.queued_user_message_history_records.clone(),
+            active_queued_user_message_edit: self.input_queue.active_queued_user_message_edit,
@@
             self.input_queue.queued_user_message_history_records.resize(
                 self.input_queue.queued_user_messages.len(),
                 UserMessageHistoryRecord::UserMessageText,
             );
+            self.input_queue.active_queued_user_message_edit =
+                input_state.active_queued_user_message_edit;
diff --git a/codex-rs/tui/src/chatwidget/input_flow.rs b/codex-rs/tui/src/chatwidget/input_flow.rs
@@
         if !self.is_session_configured() || self.is_user_turn_pending_or_running() {
-            self.input_queue.queued_user_messages.push_back(QueuedUserMessage::new(user_message, action));
-            self.input_queue.queued_user_message_history_records.push_back(UserMessageHistoryRecord::UserMessageText);
+            self.input_queue.insert_queued_user_message(
+                user_message,
+                action,
+                UserMessageHistoryRecord::UserMessageText,
+                QueuedUserMessageInsert::Back,
+            );
             self.refresh_pending_input_preview();
         } else {
+            self.input_queue.active_queued_user_message_edit = None;
             self.submit_user_message(user_message);
         }
     }
diff --git a/codex-rs/tui/src/chatwidget/input_submission.rs b/codex-rs/tui/src/chatwidget/input_submission.rs
@@
         if !self.is_session_configured() {
-            self.input_queue.queued_user_messages.push_front(QueuedUserMessage::from(user_message));
-            self.input_queue.queued_user_message_history_records.push_front(history_record);
+            self.input_queue.insert_queued_user_message(
+                user_message,
+                QueuedInputAction::Plain,
+                history_record,
+                QueuedUserMessageInsert::Front,
+            );
             self.refresh_pending_input_preview();
             return (true, None);
         }
@@
         if user_message.text.is_empty()
             && user_message.local_images.is_empty()
             && user_message.remote_image_urls.is_empty()
         {
+            self.input_queue.active_queued_user_message_edit = None;
             return (false, None);
         }
diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs
@@
+#[tokio::test]
+async fn edited_queued_message_requeues_at_original_position() {
+    let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
+    chat.bottom_pane.set_task_running(true);
+
+    for text in ["first queued", "second queued", "third queued"] {
+        chat.input_queue.queued_user_messages.push_back(UserMessage::from(text).into());
+        chat.input_queue
+            .queued_user_message_history_records
+            .push_back(UserMessageHistoryRecord::UserMessageText);
+    }
+
+    let queued_message = chat.input_queue.queued_user_messages.remove(1).unwrap();
+    let history_record = chat.input_queue.queued_user_message_history_records.remove(1).unwrap();
+    chat.input_queue.active_queued_user_message_edit = Some(QueuedUserMessageEdit { index: 1 });
+    chat.restore_user_message_to_composer(user_message_for_restore(
+        queued_message.into_user_message(),
+        &history_record,
+    ));
+
+    chat.bottom_pane.set_composer_text("second edited".to_string(), Vec::new(), Vec::new());
+    chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
+
+    assert_eq!(
+        chat.queued_user_message_texts(),
+        vec!["first queued", "second edited", "third queued"]
+    );
+    assert!(chat.input_queue.active_queued_user_message_edit.is_none());
+}
RAW_BUFFERClick to expand / collapse

What version of the Codex App are you using?

Codex Desktop 26.513.20950 (2816)

What subscription do you have?

GPT Pro

What platform is your computer?

Darwin 24.5.0 arm64 arm

What issue are you seeing?

When editing a queued message in the Codex Desktop app on macOS, pressing Enter after the edit re-adds the message at the end of the queue instead of returning it to the position it had before editing.

That makes queue editing destructive to the intended execution order. If I have queued:

  1. first queued
  2. second queued
  3. third queued

and I edit second queued, the edited message should still be item 2. Today it can come back after third queued.

Related but distinct: #16681 is about editing/removing pending steer messages. This report is about preserving the ordering of ordinary queued messages after editing.

What steps can reproduce the bug?

  1. Open Codex Desktop on macOS.
  2. Start a turn that keeps Codex working.
  3. Add several follow-up messages to the queue.
  4. Edit a queued message that is not the last queue item.
  5. Press Enter to save/requeue the edited message.
  6. Observe that the edited message is placed at the end of the queue.

What is the expected behavior?

The edited queued message should replace its original queue slot. Editing should change the message content, not reorder the queue.

Expected result:

  1. first queued
  2. second edited
  3. third queued

Actual result:

  1. first queued
  2. third queued
  3. second edited

Additional information

I could not find the Codex Desktop renderer implementation in this public repo. The public TUI code has a related queue state path: editing a queued message pops it from queued_user_messages, while requeueing normal submitted input appends to the back. A small state marker for "the queue slot currently being edited" would make the subsequent submit replace the original slot instead of always appending.

The sketch below is based on the public TUI implementation. It may need to be mapped to the equivalent Desktop queue model if the Desktop renderer owns separate queue state.

diff --git a/codex-rs/tui/src/chatwidget/user_messages.rs b/codex-rs/tui/src/chatwidget/user_messages.rs
@@
 pub(super) struct QueuedUserMessage {
     pub(super) user_message: UserMessage,
     pub(super) action: QueuedInputAction,
 }
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) struct QueuedUserMessageEdit {
+    pub(super) index: usize,
+}
@@
 pub(crate) struct ThreadInputState {
     pub(super) queued_user_messages: VecDeque<QueuedUserMessage>,
     pub(super) queued_user_message_history_records: VecDeque<UserMessageHistoryRecord>,
+    pub(super) active_queued_user_message_edit: Option<QueuedUserMessageEdit>,
     pub(super) user_turn_pending_start: bool,
 }
diff --git a/codex-rs/tui/src/chatwidget/input_queue.rs b/codex-rs/tui/src/chatwidget/input_queue.rs
@@
 use super::QueuedUserMessage;
+use super::QueuedUserMessageEdit;
 use super::UserMessage;
 use super::UserMessageHistoryRecord;
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) enum QueuedUserMessageInsert {
+    Front,
+    Back,
+}
+
 pub(super) struct InputQueueState {
     pub(super) queued_user_messages: VecDeque<QueuedUserMessage>,
+    pub(super) active_queued_user_message_edit: Option<QueuedUserMessageEdit>,
     pub(super) queued_user_message_history_records: VecDeque<UserMessageHistoryRecord>,
 }
@@
     pub(super) fn clear(&mut self) {
         self.queued_user_messages.clear();
+        self.active_queued_user_message_edit = None;
         self.queued_user_message_history_records.clear();
     }
+
+    pub(super) fn insert_queued_user_message(
+        &mut self,
+        user_message: UserMessage,
+        action: crate::bottom_pane::QueuedInputAction,
+        history_record: UserMessageHistoryRecord,
+        fallback: QueuedUserMessageInsert,
+    ) {
+        let queued_message = QueuedUserMessage::new(user_message, action);
+        if let Some(edit) = self.active_queued_user_message_edit.take() {
+            let index = edit.index.min(self.queued_user_messages.len());
+            self.queued_user_messages.insert(index, queued_message);
+            self.queued_user_message_history_records.insert(index, history_record);
+        } else if fallback == QueuedUserMessageInsert::Back {
+            self.queued_user_messages.push_back(queued_message);
+            self.queued_user_message_history_records.push_back(history_record);
+        } else {
+            self.queued_user_messages.push_front(queued_message);
+            self.queued_user_message_history_records.push_front(history_record);
+        }
+    }
 }
diff --git a/codex-rs/tui/src/chatwidget/input_restore.rs b/codex-rs/tui/src/chatwidget/input_restore.rs
@@
     pub(super) fn pop_latest_queued_user_message(&mut self) -> Option<UserMessage> {
         if let Some(user_message) = self.input_queue.queued_user_messages.pop_back() {
+            self.input_queue.active_queued_user_message_edit = Some(QueuedUserMessageEdit {
+                index: self.input_queue.queued_user_messages.len(),
+            });
             let history_record = self
                 .input_queue
                 .queued_user_message_history_records
@@
         } else {
+            self.input_queue.active_queued_user_message_edit = None;
             let user_message = self.input_queue.rejected_steers_queue.pop_back()?;
         }
     }
@@
             queued_user_messages: self.input_queue.queued_user_messages.clone(),
             queued_user_message_history_records: self.input_queue.queued_user_message_history_records.clone(),
+            active_queued_user_message_edit: self.input_queue.active_queued_user_message_edit,
@@
             self.input_queue.queued_user_message_history_records.resize(
                 self.input_queue.queued_user_messages.len(),
                 UserMessageHistoryRecord::UserMessageText,
             );
+            self.input_queue.active_queued_user_message_edit =
+                input_state.active_queued_user_message_edit;
diff --git a/codex-rs/tui/src/chatwidget/input_flow.rs b/codex-rs/tui/src/chatwidget/input_flow.rs
@@
         if !self.is_session_configured() || self.is_user_turn_pending_or_running() {
-            self.input_queue.queued_user_messages.push_back(QueuedUserMessage::new(user_message, action));
-            self.input_queue.queued_user_message_history_records.push_back(UserMessageHistoryRecord::UserMessageText);
+            self.input_queue.insert_queued_user_message(
+                user_message,
+                action,
+                UserMessageHistoryRecord::UserMessageText,
+                QueuedUserMessageInsert::Back,
+            );
             self.refresh_pending_input_preview();
         } else {
+            self.input_queue.active_queued_user_message_edit = None;
             self.submit_user_message(user_message);
         }
     }
diff --git a/codex-rs/tui/src/chatwidget/input_submission.rs b/codex-rs/tui/src/chatwidget/input_submission.rs
@@
         if !self.is_session_configured() {
-            self.input_queue.queued_user_messages.push_front(QueuedUserMessage::from(user_message));
-            self.input_queue.queued_user_message_history_records.push_front(history_record);
+            self.input_queue.insert_queued_user_message(
+                user_message,
+                QueuedInputAction::Plain,
+                history_record,
+                QueuedUserMessageInsert::Front,
+            );
             self.refresh_pending_input_preview();
             return (true, None);
         }
@@
         if user_message.text.is_empty()
             && user_message.local_images.is_empty()
             && user_message.remote_image_urls.is_empty()
         {
+            self.input_queue.active_queued_user_message_edit = None;
             return (false, None);
         }
diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs
@@
+#[tokio::test]
+async fn edited_queued_message_requeues_at_original_position() {
+    let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
+    chat.bottom_pane.set_task_running(true);
+
+    for text in ["first queued", "second queued", "third queued"] {
+        chat.input_queue.queued_user_messages.push_back(UserMessage::from(text).into());
+        chat.input_queue
+            .queued_user_message_history_records
+            .push_back(UserMessageHistoryRecord::UserMessageText);
+    }
+
+    let queued_message = chat.input_queue.queued_user_messages.remove(1).unwrap();
+    let history_record = chat.input_queue.queued_user_message_history_records.remove(1).unwrap();
+    chat.input_queue.active_queued_user_message_edit = Some(QueuedUserMessageEdit { index: 1 });
+    chat.restore_user_message_to_composer(user_message_for_restore(
+        queued_message.into_user_message(),
+        &history_record,
+    ));
+
+    chat.bottom_pane.set_composer_text("second edited".to_string(), Vec::new(), Vec::new());
+    chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
+
+    assert_eq!(
+        chat.queued_user_message_texts(),
+        vec!["first queued", "second edited", "third queued"]
+    );
+    assert!(chat.input_queue.active_queued_user_message_edit.is_none());
+}

Local checks attempted:

  • git diff --check passed.
  • cargo fmt ran, with warnings about imports_granularity = Item requiring nightly.
  • cargo test -p codex-tui edited_queued_message_requeues_at_original_position was blocked before compilation because the local Homebrew Rust toolchain is rustc 1.89.0, while current dependencies require Rust 1.91.x.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

codex - 💡(How to fix) Fix Codex Desktop editing a queued message should preserve its queue position