fix(#2426): bash wake gives status + read() pointer, not inline stdout/stderr

This commit is contained in:
damocles 2026-07-14 19:01:30 +02:00 committed by mara
commit f5c8a6a8db
2 changed files with 29 additions and 15 deletions

View file

@ -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-<id>\"` 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-<id>\"` carrying the exit status plus a `Read(<path>)` \
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 \

View file

@ -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(<path>)` 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(<path>)` 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 {