From 53a1f4c29eeef868202030bc8b117152d98f7b57 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 18 May 2026 11:33:13 -0700 Subject: [PATCH] TUI: route elicitation responses to request thread (#23241) ## Why Fixes #21894. When the TUI handles an MCP elicitation, the request payload already includes the thread that generated the elicitation. `ChatWidget::handle_elicitation_request_now` was ignoring that value and using the currently visible chat thread instead. In a multi-session TUI, that can send `resolve_elicitation` to an older visible thread rather than the session that owns the pending elicitation, producing `elicitation request not found` and leaving the prompt unresolved. ## What changed - Parse `McpServerElicitationRequestParams.thread_id` in the ChatWidget elicitation handler and use it for app-link, form, fallback approval, and auto-decline resolution paths. - Keep the existing visible-thread fallback only for malformed request payloads with an invalid thread id. - Update the invalid URL elicitation regression test so the visible thread and request thread intentionally differ. --- codex-rs/tui/src/chatwidget/tests/app_server.rs | 9 +++++---- codex-rs/tui/src/chatwidget/tool_requests.rs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index 91c5b1d1fc..0b5f989141 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -4,13 +4,14 @@ use pretty_assertions::assert_eq; #[tokio::test] async fn invalid_url_elicitation_is_declined() { let (mut chat, _app_event_tx, mut rx, _op_rx) = make_chatwidget_manual_with_sender().await; - let thread_id = ThreadId::new(); - chat.thread_id = Some(thread_id); + let visible_thread_id = ThreadId::new(); + let request_thread_id = ThreadId::new(); + chat.thread_id = Some(visible_thread_id); chat.handle_elicitation_request_now( codex_app_server_protocol::RequestId::Integer(9), codex_app_server_protocol::McpServerElicitationRequestParams { - thread_id: thread_id.to_string(), + thread_id: request_thread_id.to_string(), turn_id: Some("turn-auth".to_string()), server_name: "payments".to_string(), request: codex_app_server_protocol::McpServerElicitationRequest::Url { @@ -33,7 +34,7 @@ async fn invalid_url_elicitation_is_declined() { content: None, meta: None, }, - }) if op_thread_id == thread_id && server_name == "payments" + }) if op_thread_id == request_thread_id && server_name == "payments" ); } diff --git a/codex-rs/tui/src/chatwidget/tool_requests.rs b/codex-rs/tui/src/chatwidget/tool_requests.rs index de8fe6a704..c5c425b64f 100644 --- a/codex-rs/tui/src/chatwidget/tool_requests.rs +++ b/codex-rs/tui/src/chatwidget/tool_requests.rs @@ -341,7 +341,8 @@ impl ChatWidget { server_name: params.server_name.clone(), }); - let thread_id = self.thread_id.unwrap_or_default(); + let thread_id = ThreadId::from_string(¶ms.thread_id) + .unwrap_or_else(|_| self.thread_id.unwrap_or_default()); if let Some(params) = crate::bottom_pane::AppLinkViewParams::from_url_app_server_request( thread_id, ¶ms.server_name,