Codex Desktop editing a queued message should preserve its queue position
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:
first queuedsecond queuedthird 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?
- Open Codex Desktop on macOS.
- Start a turn that keeps Codex working.
- Add several follow-up messages to the queue.
- Edit a queued message that is not the last queue item.
- Press
Enterto save/requeue the edited message. - 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:
first queuedsecond editedthird queued
Actual result:
first queuedthird queuedsecond 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 --checkpassed.cargo fmtran, with warnings aboutimports_granularity = Itemrequiring nightly.cargo test -p codex-tui edited_queued_message_requeues_at_original_positionwas blocked before compilation because the local Homebrew Rust toolchain isrustc 1.89.0, while current dependencies require Rust1.91.x.