← всі звіти · read-2026-05-01T163803Z-watchdog-subagent-detection.md

from: VPS Claude topic: watchdog-subagent-detection priority: normal date: 2026-05-01T16:18:00Z

Watchdog: фікс false-restart при довгих subagent-runах

Проблема (що сталося 2026-05-01 ~16:12)

Sergey попросив research про travel-itinerary skills. Я викликав Agent (subagent) для веб-пошуку — він працював ~35с. У цей час parent-сесійний jsonl не писався (немає нових tool_use/tool_result у мене), тому latest_jsonl_mtime показував idle ~120s.

Watchdog побачив:

Але насправді бот був "alive and working" — просто чекав на subagent.

Поточна структура (/srv/services/claude-watchdog/watchdog.py)

check_bot_reply_gap() — line 275-355:

Я перезапустив сервіс щоб підхопити нову версію (1h threshold) — це знизить частоту false-positives, але не вирішує root cause.

Запропонований фікс (структурний)

У latest_jsonl_mtime() або новій функції — детектити pending tool_use без matching tool_result в останньому jsonl. Це ознака "бот викликав інструмент і чекає на відповідь" (особливо Agent — субагенти можуть йти 30с-5хв).

def has_pending_tool_use(lookback_lines=200):
    """True якщо останній assistant tool_use не має matching tool_result."""
    jsonls = sorted(CLAUDE_SESSIONS_DIR.glob("*.jsonl"), key=lambda p: p.stat().st_mtime)
    if not jsonls:
        return False
    last = jsonls[-1]
    pending_ids = set()
    with open(last, "r", encoding="utf-8", errors="ignore") as f:
        lines = f.readlines()[-lookback_lines:]
    for line in lines:
        try:
            msg = json.loads(line)
            content = msg.get("message", {}).get("content", [])
            if not isinstance(content, list):
                continue
            for block in content:
                if block.get("type") == "tool_use":
                    pending_ids.add(block["id"])
                elif block.get("type") == "tool_result":
                    pending_ids.discard(block.get("tool_use_id"))
        except Exception:
            continue
    return bool(pending_ids)

Потім у check_bot_reply_gap (line 302):

if jsonl_idle_sec < 120 or has_pending_tool_use():
    return  # бот або пише, або чекає на tool — не стук

Action required

Контекст