From f5c8a6a8dbf1de9b9d5bd11bb0dcdbb3cde39488 Mon Sep 17 00:00:00 2001 From: damocles Date: Tue, 14 Jul 2026 19:01:30 +0200 Subject: [PATCH] fix(#2426): bash wake gives status + read() pointer, not inline stdout/stderr --- hive-bash-mcp/src/bin/mcp.rs | 5 +++-- hive-bash-mcp/src/runner.rs | 39 ++++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/hive-bash-mcp/src/bin/mcp.rs b/hive-bash-mcp/src/bin/mcp.rs index 10ba08b8..ab75ca75 100644 --- a/hive-bash-mcp/src/bin/mcp.rs +++ b/hive-bash-mcp/src/bin/mcp.rs @@ -238,8 +238,9 @@ impl BashMcp { #[tool( description = "Run a shell command in the background. Returns a task ID immediately — \ do NOT wait inline. When the command finishes, the harness fires a wake with \ - `from: \"bash-task-\"` and the exit code + last stdout lines in the body; \ - handle it on a future turn. Use `status` to poll the task status within \ + `from: \"bash-task-\"` carrying the exit status plus a `Read()` \ + pointer to the captured `.out`/`.err` files (not the output text itself — \ + read only what you need); handle it on a future turn. Use `status` to poll the task status within \ the same turn if needed. `timeout_secs` defaults to `None` (no timeout) — \ task runs until natural exit; pass an explicit value to kill after N seconds. \ Pass `wait_seconds` (capped at 30) to wait inline for fast commands: when the \ diff --git a/hive-bash-mcp/src/runner.rs b/hive-bash-mcp/src/runner.rs index d7228012..89045a28 100644 --- a/hive-bash-mcp/src/runner.rs +++ b/hive-bash-mcp/src/runner.rs @@ -575,9 +575,12 @@ async fn run_task(mut task: TaskFile, socket: &Path) { return; } - let out_snippet = stdout_tail.as_deref().unwrap_or("").trim(); - let err_snippet = stderr_tail.as_deref().unwrap_or("").trim(); - send_wake(socket, &id, &summary, Some((out_snippet, err_snippet))).await; + // Pass only whether each stream produced (trimmed) output — `send_wake` + // emits a `Read()` pointer to the captured `.out`/`.err` files + // rather than inlining the tail into the wake body. + let has_stdout = !stdout_tail.as_deref().unwrap_or("").trim().is_empty(); + let has_stderr = !stderr_tail.as_deref().unwrap_or("").trim().is_empty(); + send_wake(socket, &id, &summary, Some((has_stdout, has_stderr))).await; } /// Run `bash -c cmd` in its own process group, streaming output to files. @@ -730,21 +733,31 @@ pub(crate) async fn send_wake( socket: &Path, id: &str, summary: &str, - output: Option<(&str, &str)>, + // `(has_stdout, has_stderr)` — whether the task produced non-empty + // stdout / stderr. `None` for the interrupted path (no captured output + // to point at). Deliberately NOT the output text: the wake gives the + // agent the status + a `Read()` pointer to the captured files + // rather than inlining the tail, which would flood the agent's context + // on every task completion. + output: Option<(bool, bool)>, ) { + use std::fmt::Write as _; use tokio::io::{AsyncBufReadExt as _, BufReader}; use tokio::net::UnixStream; let mut body = format!("bash task `{id}` finished: {summary}"); - if let Some((stdout, stderr)) = output { - if !stdout.is_empty() { - body.push_str("\n\nstdout:\n```\n"); - body.push_str(stdout); - body.push_str("\n```"); + if let Some((has_stdout, has_stderr)) = output + && (has_stdout || has_stderr) + { + body.push_str("\n\noutput captured — read the full text with:"); + if has_stdout { + let _ = write!(body, "\n Read({})", crate::paths::task_out(id).display()); } - if !stderr.is_empty() { - body.push_str("\n\nstderr:\n```\n"); - body.push_str(stderr); - body.push_str("\n```"); + if has_stderr { + let _ = write!( + body, + "\n Read({}) # stderr", + crate::paths::task_err(id).display() + ); } } let req = hive_sh4re::AgentRequest::Wake {