Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
Author SHA1 Message Date
damocles
3fad315ac3 hive-bash-mcp: drop the redundant output-path line from status's description
Same principle as the enum-enumeration fix, mara caught it in review:
the description said where full output lives, but format_task's own
answer already names the exact path when output was actually
truncated — the description doesn't need to say it too.
2026-09-13 15:56:10 +02:00
damocles
a54f711cc5 hive-bash-mcp: explain the status when returning it, not up front
Mirrors #4333's split on the sibling hive-subagent-mcp surface (mara's
ruling there, quoted on #4336: "dont explain all the possible states
that can be returned in the tool description. instead explain the
status when returning it").

status_explanation() carries what each TaskStatus means and what to do
about it, appended to every format_task answer. The status tool's
description shrinks to what it's for; a caller only ever sees one
state at a time and now learns what it means without cross-referencing
anything else.
2026-09-13 15:48:28 +02:00

View file

@ -30,9 +30,35 @@ fn status_str(status: &TaskStatus) -> &'static str {
} }
} }
/// What a state means and what the caller should do about it — carried on
/// every `format_task` answer instead of the tool description enumerating
/// all six up front, so a caller who only ever sees one state at a time
/// still learns what it means without reading anything else.
fn status_explanation(status: &TaskStatus) -> &'static str {
match status {
TaskStatus::Pending => "queued, not started yet",
TaskStatus::Running => {
"still going — check back later, or pass wait_seconds to block briefly"
}
TaskStatus::Done => "finished normally",
TaskStatus::TimedOut => "exceeded timeout_secs and was killed",
TaskStatus::Interrupted => {
"the daemon restarted mid-task — the process is gone, no result to report"
}
TaskStatus::Killed => {
"killed on request via `kill` — a signal was sent to the whole process group"
}
}
}
/// Format a `TaskFile` as the human-readable status string claude sees. /// Format a `TaskFile` as the human-readable status string claude sees.
fn format_task(task: &TaskFile) -> String { fn format_task(task: &TaskFile) -> String {
let mut out = format!("task `{}`: status={}", task.id, status_str(&task.status)); let mut out = format!(
"task `{}`: status={} ({})",
task.id,
status_str(&task.status),
status_explanation(&task.status)
);
if let Some(code) = task.exit_code { if let Some(code) = task.exit_code {
let _ = write!(out, ", exit={code}"); let _ = write!(out, ", exit={code}");
@ -189,10 +215,9 @@ impl BashMcp {
} }
#[tool( #[tool(
description = "Check the status of a background bash task by its ID (from `run`). \ description = "Check the status of a background bash task by its ID (from `run`). The \
Returns the current status (pending/running/done/timed_out/interrupted/killed), exit \ answer names the state it found and what to do about it, plus the exit code if \
code if finished, and a tail of stdout/stderr. Full output lives in \ finished and a tail of stdout/stderr. \
`harness/bash-tasks/<id>.out` / `.err`. \
Pass `wait_seconds` (capped at 30) to wait inline for the task to finish: when the \ Pass `wait_seconds` (capped at 30) to wait inline for the task to finish: when the \
task finishes within the window the full status is returned immediately. Useful to \ task finishes within the window the full status is returned immediately. Useful to \
avoid a separate round-trip when the task is expected to finish soon." avoid a separate round-trip when the task is expected to finish soon."
@ -332,4 +357,31 @@ mod status_hint_tests {
assert!(out.contains("status=done")); assert!(out.contains("status=done"));
}); });
} }
#[test]
fn every_status_answer_names_its_state_and_the_next_move() {
// Pins the split this module's doc describes: the tool description
// says what the tool is for, and each individual answer — not a
// list the caller has to cross-reference — says what state it got
// and what to do about it. One state's explanation text is
// distinct enough from the others that a copy-paste regression
// (e.g. `Killed` picking up `TimedOut`'s wording) would fail this.
with_harness_dir(|| {
for status in [
TaskStatus::Pending,
TaskStatus::Running,
TaskStatus::Done,
TaskStatus::TimedOut,
TaskStatus::Interrupted,
TaskStatus::Killed,
] {
let explanation = super::status_explanation(&status);
let out = format_task(&task(status.clone()));
assert!(
out.contains(explanation),
"status={status:?} explanation missing from: {out}"
);
}
});
}
} }