From bd04c6acd7c773058aa213fe7ca554b0d4c25f63 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 13 Apr 2026 16:17:06 +0000 Subject: [PATCH] fix: capture test output with background pipe draining instead of Stdio::inherit Stdio::inherit sent test output to server stdout, making it invisible to agents calling run_tests via MCP. Switch back to Stdio::piped with background drain threads (same pattern as gates.rs) to capture output without the pipe deadlock that caused the original switch to inherit. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/src/http/mcp/shell_tools.rs | 56 +++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/server/src/http/mcp/shell_tools.rs b/server/src/http/mcp/shell_tools.rs index 315a8d4d..99bafb86 100644 --- a/server/src/http/mcp/shell_tools.rs +++ b/server/src/http/mcp/shell_tools.rs @@ -399,12 +399,14 @@ pub(super) async fn tool_run_tests(args: &Value, ctx: &AppContext) -> Result Result Result { - // Done — collect results. - let result = collect_child_result(child, status); + // Done — join drain threads and collect output. + jobs.remove(&working_dir); + let stdout = stdout_handle + .take() + .and_then(|h| h.join().ok()) + .unwrap_or_default(); + let stderr = stderr_handle + .take() + .and_then(|h| h.join().ok()) + .unwrap_or_default(); + let combined = format!("{stdout}{stderr}"); + let (tests_passed, tests_failed) = parse_test_counts(&combined); + let truncated = truncate_output(&combined, MAX_OUTPUT_LINES); + let passed = status.success(); + let exit_code = status.code().unwrap_or(-1); crate::slog!( "[run_tests] Test job for {} finished (pid {}, passed={})", working_dir.display(), pid, - result.passed + passed ); - jobs.remove(&working_dir); - return format_test_result(&result); + return serde_json::to_string_pretty(&json!({ + "passed": passed, + "exit_code": exit_code, + "timed_out": false, + "tests_passed": tests_passed, + "tests_failed": tests_failed, + "output": truncated, + })) + .map_err(|e| format!("Serialization error: {e}")); } Ok(None) => { // Still running — check timeout.