cyberine
UsageRun recipes

Idle-stream watchdog — abort a stalled stream, fleet worker + chat (T562, T570)

Claude Code v2.1.196 made a streaming idle watchdog default-on: it aborts and retries a response stream that produces no events for 5 minutes. LoopAgentAdapter (fleet workers) AND the interactive chat planner (plannerTurn, T570) share the same StreamWatchdog protection — a hung upstream (connection open, zero chunks) no longer stalls either surface until the outer wall-clock cap (fleet) or forever with only the spinner color drifting (chat).

How it works:

  1. The idle timer resets on every received stream event (chunk); it is armed right before the request is issued, so a provider that never even sends response headers is also caught.
  2. On CYBERINE_STREAM_WATCHDOG_MS of silence, the request is aborted on a PRIVATE AbortController — distinct from the caller's own AbortSignal. What happens next differs by surface:
    • Fleet workers (LoopAgentAdapter): one retry on a fresh connection to the SAME model first; if that also stalls, escalate through the SAME fallback-model retry path used for 429/402/5xx overload errors (see "Fallback model chain" above) — trying the next configured model, or failing like any exhausted overload retry when none remain.
    • Interactive chat (plannerTurn): no auto-retry and no model switch — the stall is surfaced immediately as a clear, actionable error message (not AbortError-shaped, so it is never mistaken for a silent user-initiated stop) telling the user to retry the message or switch model. This intentionally does not duplicate the fleet's model-fallback logic; it only respects whatever fallback the caller already applies on its own (e.g. the chat's model-cycle toggle).
  3. A caller-initiated abort (task stop / Ctrl+B / Esc) always wins over the watchdog on both surfaces: the run is reported cancelled (fleet) or the abort propagates as the usual silent stop (chat), with no retry, even if the watchdog happened to be armed at the same time.

Run recipe:

# Lower the threshold for a faster-failing dev loop (default is 5 minutes):
CYBERINE_STREAM_WATCHDOG_MS=60000 cyberine run "..."
CYBERINE_STREAM_WATCHDOG_MS=60000 cyberine chat

# Disable the watchdog entirely (opt-out, mirrors CLAUDE_ENABLE_STREAM_WATCHDOG=0):
CYBERINE_STREAM_WATCHDOG_MS=0 cyberine run "..."

Env vars:

VarDefaultPurpose
CYBERINE_STREAM_WATCHDOG_MS300000Idle-stream watchdog threshold in ms, shared by fleet workers and chat. 0 disables it

Smoke test: npm run selftest:stream-watchdog (model-free; a local fake SSE server proves the retry-on-stall, caller-abort-wins, opt-out, and idle-timer-cleared-on-a-genuine-network-error behaviors without a live provider call).