A `status` or `run` call whose inline `wait_seconds` poll observes a terminal task hands the caller the full result in that same tool response. The completion wake fired unconditionally regardless, producing a redundant `bash-task-<id>` inbox message for information the agent already has. Add a one-shot, in-memory wake-suppression registry in hive-bash-mcp's runner: `wait_for_task` (shared by both BashRun's and BashStatus's inline-wait paths) marks a task's wake suppressed the moment it observes a terminal state; `run_task`'s completion handler consumes that flag before calling `send_wake` and skips the wake if set. In-memory only (daemon restart wipes it) — fine, since a task still running across a restart is separately marked `interrupted` on boot and gets its own fresh wake. Narrow best-effort race window between the terminal write and the wake send; acceptable given this daemon's existing best-effort delivery tolerance elsewhere. docs/tools/bash.md updated to describe the new suppression behavior.
5.5 KiB
Bash execution tools
Background shell execution via hive-bash-mcp. Tools land as
mcp__bash__<tool> (the MCP server name is bash, not hyperhive).
Available on every agent unconditionally — harness-base.nix always
injects bash into hyperhive.extraMcpServers (with allowedTools = ["*"]), so mcp__bash__* is in --allowedTools for every claude
invocation regardless of tool groups.
Tools
run(cmd, timeout_secs?, wait_seconds?, name?)
Submit a shell command for background execution (runs via bash).
Stdout and stderr stream to harness/bash-tasks/<id>.{out,err}.
When the task completes (or times out, or the process errors), the
harness fires a wake with from: "bash-task-<id>"; the body contains
the exit code and last stdout lines. Handle the completion on a future
turn — unless wait_seconds already delivered the terminal result
inline, in which case the wake is suppressed (see status below).
timeout_secs— kill the task after N seconds and mark ittimed_out. Omit for no timeout (runs until natural exit).wait_seconds— inline poll before returning (capped at 30). When the task finishes within the window the full status is returned immediately and no wake is fired; when the window expires the task keeps running and the normaltask started: id=<id>response is returned. Defaults to 3 — passwait_seconds: 0to disable inline waiting and always get the immediate response.name— optional caller-chosen task id. When set it replaces the auto-generated hex id, so it surfaces in the wakefrom(bash-task-<name>), instatus(<name>)lookups, and in the loose-ends list — a memorable label instead of an opaque id. A name is reusable once its previous task has finished; submitting a name whose task is stillpending/runningis rejected. Allowed characters: ASCII letters, digits,.,_,-(max 64). Omit for the auto-generated id.
Exposed as mcp__bash__run.
status(id, wait_seconds?)
Poll the status of a task submitted with run. Returns:
status—pending/running/done/timed_out/interrupted/killedexit_code— set when done- run duration
- last 4 KiB of stdout and stderr (full output in the
.out/.errfiles)
wait_seconds — optional inline poll (capped at 30): when the 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.
Any status call (waited or not) that observes a terminal task
suppresses that task's completion wake — you already have the result
in this response, so no redundant bash-task-<id> inbox message
follows (#2270). Narrow best-effort race: a status/run inline wait
that resolves in the same instant the task actually finishes can still
occasionally get both.
Tasks marked interrupted had their process killed by a harness
restart; a best-effort wake was still sent so the agent is not
silently blocked.
Exposed as mcp__bash__status.
kill(id, force?)
Stop a running or pending task by its ID (from run). Fire-and-forget:
sends the signal and returns without waiting — handle the completion
wake (from: "bash-task-<id>") on a future turn.
force: false(default) — SIGINT to the task's process group (graceful; lets the process clean up). The whole process group is signalled, so children spawned by the shell (cargo, nix, etc.) are also stopped.force: true— SIGKILL.
If a SIGINT'd task doesn't exit, call kill again with force: true.
A still-pending task is cancelled before it starts. The task ends as
killed and fires the usual completion wake.
Exposed as mcp__bash__kill.
Namespace note
run and status live in the bash MCP server, not hyperhive. So
the tool names in claude are mcp__bash__run and mcp__bash__status.
The Bash built-in tool is blocked — all shell execution goes through
this structured path so tasks get task-id tracking and structured output.
Architecture
The bash tooling follows the same daemon + stdio-bridge pattern as the matrix MCP:
-
hive-bash-daemon— long-running process (one per agent container, systemd service inharness-base.nix). Owns subprocess management, output file writing,mcp-loose-ends/state, and wake signal delivery. Listens on/run/hive-bash/socketinside the container. -
hive-bash-mcp— stdio bridge spawned by claude per turn (declared inhyperhive.extraMcpServers.bash). Connects to the daemon socket and forwardsrun/statustool calls. Has no subprocess management logic of its own.
This split keeps claude's turn-local MCP bridge lightweight while the daemon tracks long-running tasks that outlive a single turn.
Transient wake (bypass broker sqlite)
When a bash task finishes, hive-bash-daemon sends the wake signal via
the agent's per-agent socket as a transient wake (AgentRequest::Wake
with transient: true). This bypasses broker sqlite for lower latency —
the same mechanism used by matrix events. The message is delivered
directly to the harness without touching the persistent message store.
Relationship to the execution tool group
ToolGroup::Execution exists and appears in AGENT_DEFAULT, but its
tools() returns ["run", "status"] which the harness expands to
mcp__hyperhive__run / mcp__hyperhive__status — tools that don't
exist in the hyperhive MCP server (dead entries). Removing execution
from an agent's groups has no effect on bash availability. Bash is
registered separately via the extraMcpServers path described above.