From 0896bf6fc05ead454888b90044e1a08f99b6d778 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sun, 26 Jul 2026 22:13:59 +0000 Subject: [PATCH] Skip inactive TUI threads without pending user interaction (#35525) ## What changed Only collect buffered requests from inactive threads whose event store reports pending user input or approval. This keeps unrelated requests from being considered when pending interactions are surfaced after leaving a side conversation. ## Testing Extend the side-conversation routing test with an unrelated dynamic tool request and verify that only the pending subagent approval is selected. GitOrigin-RevId: 388968277bde34bedc43187c1d1746d76166131e --- codex-rs/tui/src/app/tests.rs | 33 ++++++++++++++++++++------ codex-rs/tui/src/app/thread_routing.rs | 3 +++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index e27e82522d2..970300b8918 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -2961,6 +2961,8 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()> ThreadId::from_string("00000000-0000-0000-0000-000000000022").expect("valid thread"); let agent_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000033").expect("valid thread"); + let quiet_thread_id = + ThreadId::from_string("00000000-0000-0000-0000-000000000044").expect("valid thread"); app.primary_thread_id = Some(main_thread_id); app.active_thread_id = Some(side_thread_id); @@ -2986,14 +2988,27 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()> /*is_closed*/ false, ); - app.enqueue_thread_request( + let pending_approval = exec_approval_request( agent_thread_id, - exec_approval_request( - agent_thread_id, - "turn-approval", - "call-approval", - /*approval_id*/ None, - ), + "turn-approval", + "call-approval", + /*approval_id*/ None, + ); + app.enqueue_thread_request(agent_thread_id, pending_approval.clone()) + .await?; + app.enqueue_thread_request( + quiet_thread_id, + ServerRequest::DynamicToolCall { + request_id: AppServerRequestId::Integer(99), + params: codex_app_server_protocol::DynamicToolCallParams { + thread_id: quiet_thread_id.to_string(), + turn_id: "turn-quiet".to_string(), + call_id: "call-quiet".to_string(), + namespace: None, + tool: "ignored-tool".to_string(), + arguments: serde_json::json!({}), + }, + }, ) .await?; @@ -3005,6 +3020,10 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()> app.side_threads.remove(&side_thread_id); app.active_thread_id = Some(main_thread_id); + assert_eq!( + app.pending_inactive_thread_requests().await, + vec![(agent_thread_id, pending_approval)] + ); app.surface_pending_inactive_thread_interactive_requests() .await?; diff --git a/codex-rs/tui/src/app/thread_routing.rs b/codex-rs/tui/src/app/thread_routing.rs index f6490dcef8b..a87d2b5a0fc 100644 --- a/codex-rs/tui/src/app/thread_routing.rs +++ b/codex-rs/tui/src/app/thread_routing.rs @@ -389,6 +389,9 @@ impl App { } let store = store.lock().await; + if store.side_parent_pending_status().is_none() { + continue; + } requests.extend( store .pending_replay_requests()