From 14969e35fa7ec8b62c1ac33a93e17a829145cc39 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 16 Jun 2026 15:07:55 -0600 Subject: [PATCH] fix: buffer tool stdout as well as stderr so that any tools that error to stdout are captured and included in the response to the model, enabling the model to see what went wrong and to reason about how to fix it. --- src/function/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/function/mod.rs b/src/function/mod.rs index 79b77b5..df47e52 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -1292,11 +1292,13 @@ pub fn run_llm_function( let mut buffer = [0; 1024]; let mut reader = stdout; let mut out = io::stdout(); + let mut buf = Vec::new(); while let Ok(n) = reader.read(&mut buffer) { if n == 0 { break; } let chunk = &buffer[0..n]; + buf.extend_from_slice(chunk); let mut last_pos = 0; for (i, &byte) in chunk.iter().enumerate() { if byte == b'\n' { @@ -1310,6 +1312,7 @@ pub fn run_llm_function( } let _ = out.flush(); } + buf }); let stderr_thread = std::thread::spawn(move || { @@ -1342,18 +1345,22 @@ pub fn run_llm_function( let status = child .wait() .map_err(|err| anyhow!("Unable to run {command_name}, {err}"))?; - let _ = stdout_thread.join(); + let stdout_bytes = stdout_thread.join().unwrap_or_default(); let stderr_bytes = stderr_thread.join().unwrap_or_default(); let exit_code = status.code().unwrap_or_default(); if exit_code != 0 { let stderr = String::from_utf8_lossy(&stderr_bytes).trim().to_string(); + let stdout = String::from_utf8_lossy(&stdout_bytes).trim().to_string(); let tool_error_message = format!("Tool call '{command_name}' exited with code {exit_code}"); eprintln!("{}", warning_text(&format!("⚠️ {tool_error_message} ⚠️"))); let mut error_json = json!({"tool_call_error": tool_error_message}); if !stderr.is_empty() { error_json["stderr"] = json!(stderr); } + if !stdout.is_empty() { + error_json["stdout"] = json!(stdout); + } debug!("Tool call error: {error_json:?}"); return Ok(Some(error_json.to_string())); }