From a5783f90c936cb2ce51e8a4a98bdc15b1a88dc7c Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 13 Apr 2026 08:35:17 -0700 Subject: [PATCH] Fix custom tool output cleanup on stream failure (#17470) Addresses #16255 Problem: Incomplete Responses streams could leave completed custom tool outputs out of cleanup and retry prompts, making persisted history inconsistent and retries stale. Solution: Route stream and output-item errors through shared cleanup, and rebuild retry prompts from fresh session history after the first attempt. --- codex-rs/core/src/codex.rs | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index a3a674cb6..7311df280 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -6838,12 +6838,6 @@ async fn run_sampling_request( let base_instructions = sess.get_base_instructions().await; - let prompt = build_prompt( - input, - router.as_ref(), - turn_context.as_ref(), - base_instructions, - ); let tool_runtime = ToolCallRuntime::new( Arc::clone(&router), Arc::clone(&sess), @@ -6861,7 +6855,21 @@ async fn run_sampling_request( ) .await; let mut retries = 0; + let mut initial_input = Some(input); loop { + let prompt_input = if let Some(input) = initial_input.take() { + input + } else { + sess.clone_history() + .await + .for_prompt(&turn_context.model_info.input_modalities) + }; + let prompt = build_prompt( + prompt_input, + router.as_ref(), + turn_context.as_ref(), + base_instructions.clone(), + ); let err = match try_run_sampling_request( tool_runtime.clone(), Arc::clone(&sess), @@ -7668,7 +7676,8 @@ async fn try_run_sampling_request( }; let event = match event { - Some(res) => res?, + Some(Ok(event)) => event, + Some(Err(err)) => break Err(err), None => { break Err(CodexErr::Stream( "stream closed before response.completed".into(), @@ -7739,9 +7748,14 @@ async fn try_run_sampling_request( | ResponseItem::Other => false, }; - let output_result = handle_output_item_done(&mut ctx, item, previously_active_item) - .instrument(handle_responses) - .await?; + let output_result = + match handle_output_item_done(&mut ctx, item, previously_active_item) + .instrument(handle_responses) + .await + { + Ok(output_result) => output_result, + Err(err) => break Err(err), + }; if let Some(tool_future) = output_result.tool_future { in_flight.push_back(tool_future); }