diff --git a/codex-rs/codex-mcp/src/elicitation.rs b/codex-rs/codex-mcp/src/elicitation.rs index 4a9064da0d..08257d8fbe 100644 --- a/codex-rs/codex-mcp/src/elicitation.rs +++ b/codex-rs/codex-mcp/src/elicitation.rs @@ -418,7 +418,7 @@ impl ElicitationRequestManager { router: router.clone(), key: request_key, }; - let _ = tx_event + tx_event .send(Event { id: "mcp_elicitation_request".to_string(), msg: EventMsg::ElicitationRequest(ElicitationRequestEvent { @@ -428,7 +428,8 @@ impl ElicitationRequestManager { request, }), }) - .await; + .await + .context("failed to deliver MCP elicitation request")?; rx.await .context("elicitation request channel closed unexpectedly") } diff --git a/codex-rs/codex-mcp/src/elicitation_tests.rs b/codex-rs/codex-mcp/src/elicitation_tests.rs index f663706c9e..de16aa007a 100644 --- a/codex-rs/codex-mcp/src/elicitation_tests.rs +++ b/codex-rs/codex-mcp/src/elicitation_tests.rs @@ -118,6 +118,52 @@ async fn assert_declined(marker: Value, response: Option) { assert!(events.is_empty()); } +#[test] +fn closed_event_channel_immediately_cleans_up_pending_elicitation() { + let active_elicitations = Arc::new(AtomicUsize::new(0)); + let registrations = active_elicitations.clone(); + let lifecycle = ElicitationLifecycle::new(move || { + registrations.fetch_add(/*val*/ 1, Relaxed); + LifecycleRegistration(registrations.clone()) + }); + let (manager, events, sender) = elicitation_fixture( + AskForApproval::OnRequest, + PermissionProfile::Disabled, + /*reviewer*/ None, + ); + assert!(manager.update( + AskForApproval::OnRequest, + PermissionProfile::Disabled, + /*reviewer*/ None, + Some(lifecycle), + )); + drop(events); + + let elicitation = Elicitation::Mcp(ElicitRequestParams::FormElicitationParams { + meta: None, + message: "Review this request".to_string(), + requested_schema: ElicitationSchema::builder().build().unwrap(), + }); + let error = sender(RequestId::Number(7), elicitation) + .now_or_never() + .expect("closed event channel must not leave an elicitation pending") + .expect_err("closed event channel must fail the elicitation"); + + assert_eq!( + error.to_string(), + "failed to deliver MCP elicitation request" + ); + assert!( + manager + .router + .requests + .lock() + .expect("pending request router should be available") + .is_empty() + ); + assert_eq!(active_elicitations.load(Relaxed), 0); +} + #[tokio::test] async fn strict_auto_review_respects_explicit_elicitation_denials() { for policy in [