diff --git a/codex-rs/app-server-client/src/remote.rs b/codex-rs/app-server-client/src/remote.rs index 51015f8e7b34..c8e9a93d2e69 100644 --- a/codex-rs/app-server-client/src/remote.rs +++ b/codex-rs/app-server-client/src/remote.rs @@ -612,14 +612,9 @@ impl RemoteAppServerClient { .send(RemoteClientCommand::Shutdown { response_tx }) .await .is_ok() - && let Ok(command_result) = timeout(SHUTDOWN_TIMEOUT, response_rx).await + && let Ok(Ok(close_result)) = timeout(SHUTDOWN_TIMEOUT, response_rx).await { - command_result.map_err(|_| { - IoError::new( - ErrorKind::BrokenPipe, - "remote app-server shutdown channel is closed", - ) - })??; + close_result?; } if let Err(_elapsed) = timeout(SHUTDOWN_TIMEOUT, &mut worker_handle).await { @@ -981,4 +976,24 @@ mod tests { skipped: 1 })); } + + #[tokio::test] + async fn shutdown_tolerates_worker_exit_after_command_is_queued() { + let (command_tx, mut command_rx) = mpsc::channel(1); + let (_event_tx, event_rx) = mpsc::channel(1); + let worker_handle = tokio::spawn(async move { + let _ = command_rx.recv().await; + }); + let client = RemoteAppServerClient { + command_tx, + event_rx, + pending_events: VecDeque::new(), + worker_handle, + }; + + client + .shutdown() + .await + .expect("shutdown should complete when worker exits first"); + } }