Watch
0
0
Fork
You've already forked hyperhive
0

hive-claude 0.1.4: the idle watchdog stops claude with SIGINT

hive-claude 0.1.4 (hive-claude #28) sends the idle watchdog's signal as
SIGINT to claude's process group instead of SIGKILL. It then waits for
claude to exit, with no SIGKILL fallback. claude stops the commands its
Bash tool started when it shuts down on SIGINT, and a SIGKILL left them
running. This is the last SIGKILL of a claude process from #4999.

hive-agent's `idle_timeout` -> `Error::IdleTimeout` -> `TurnError::ApiStall`
path is unchanged. The difference is that the error now arrives only
once claude has exited. A claude that ignores the SIGINT keeps the turn
open until it is killed by hand or the unit stops.

The Cargo.lock change is only hive-claude's version and checksum. A plain
`cargo update -p hive-claude --precise 0.1.4` also moved seven crates'
Windows-only `windows-sys` edges from 0.61.2 to 0.52.0, unrelated to this
bump. The minimal lock passes `--locked`.

The turn-loop and web-UI docs, and hive-agent's watchdog docs and stall
note, now say SIGINT instead of "killed". That includes the ACP spots
that the previous commit's `Drop` change made stale.
This commit is contained in:
atlas 2026-10-10 21:54:07 +02:00 • committed by mara
commit f3860f3497
5 changed files with 17 additions and 13 deletions

View file

@ -50,8 +50,8 @@ const DEFAULT_SESSION_TITLE: &str = "hive-session";
/// capacity limits.
const DEFAULT_RATE_LIMIT_SLEEP_SECS: u64 = 300;
/// Idle watchdog window: kill claude and surface `ApiStall` if it produces no
/// stdout for this long. The timer resets on every stdout line, so a large but
/// Idle watchdog window: send claude SIGINT and surface `ApiStall` once it
/// exits, if it produces no stdout for this long. The timer resets on every stdout line, so a large but
/// still-streaming turn is never cut — only a complete silence trips it.
/// Default is 10 minutes: long enough for legitimate big-context turns, short
/// enough to cut a multi-retry Anthropic connection storm (atlas telemetry:
@ -189,14 +189,14 @@ pub enum TurnError {
/// the wake message, the serve loop requeues it so the next turn retries;
/// no status park.
SessionNotFound,
/// The harness-side idle watchdog killed claude after `turn_idle_secs()` of
/// The harness-side idle watchdog stopped claude after `turn_idle_secs()` of
/// output silence — indicative of an Anthropic API stall (e.g. a multi-retry
/// connection storm burning minutes of wall-clock with no stream progress).
/// The serve loop parks for `stall_sleep_secs()` and requeues, like the
/// rate-limit path — NOT a crash.
ApiStall,
/// The ACP backend's idle watchdog stopped a turn that sent nothing for
/// `turn_idle_secs()`: the agent was sent `session/cancel`, and killed if
/// `turn_idle_secs()`: the agent was sent `session/cancel`, and SIGINT if
/// it ignored that. Carries what happened. An ACP agent retrying a failing
/// provider (e.g. HTTP 429) without reporting it ends up here. Handled
/// like [`Self::ApiStall`].
@ -558,11 +558,11 @@ pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) {
bus.emit(LiveEvent::TurnEnd {
ok: false,
note: Some(format!(
"claude killed after {}s of output silence — API stall suspected, parking + requeueing",
"claude stopped with SIGINT after {}s of output silence — API stall suspected, parking + requeueing",
turn_idle_secs()
)),
});
tracing::warn!("turn killed: API stall (idle watchdog)");
tracing::warn!("turn stopped: API stall (idle watchdog)");
}
Err(TurnError::AgentStall(note)) => {
bus.emit(LiveEvent::TurnEnd {