# 🪦 #1882 — watchdog zombie reap

`src/hooks/src/subagent-stop/watchdog.ts` read every entry in `.claude/logs/subagent-spawns.jsonl` (last 20), so spawns from prior sessions (terminal closed, /clear, OOM) accumulated forever and fired CRITICAL warnings about agents that hadn't existed for days. This PR adds a 24h age cap.

## Captured noise (real session output, 2026-05-19→20)

    CRITICAL: Agent "agents-view-editor"          running for 6823min — may be hung.
    CRITICAL: Agent "manifest-reconciler"         running for 6822min — may be hung.
    CRITICAL: Agent "opus-sweeper"                running for 6822min — may be hung.
    CRITICAL: Agent "agents-view-editor"          running for 6821min — may be hung.
    CRITICAL: Agent "agents-view-editor"          running for 6818min — may be hung.
    CRITICAL: Agent "manifest-reconciler"         running for 6818min — may be hung.
    CRITICAL: Agent "opus-sweeper"                running for 6818min — may be hung.
    CRITICAL: Agent "Explore"                     running for 5493min — may be hung.
    CRITICAL: Agent "Explore"                     running for 5493min — may be hung.
    CRITICAL: Agent "ork:code-quality-reviewer"   running for 2849min — may be hung.
    CRITICAL: Agent "ork:security-auditor"        running for 2849min — may be hung.
    CRITICAL: Agent "general-purpose"             running for 2367min — may be hung.
    CRITICAL: Agent "general-purpose"             running for 2367min — may be hung.
    CRITICAL: Agent "general-purpose"             running for 2367min — may be hung.
    CRITICAL: Agent "general-purpose"             running for 1764min — may be hung.
    CRITICAL: Agent "general-purpose"             running for 1744min — may be hung.
    CRITICAL: Agent "general-purpose"             running for 1741min — may be hung.
    CRITICAL: Agent "Explore"                     running for 1254min — may be hung.
    CRITICAL: Agent "ork:backend-system-architect" running for 1253min — may be hung.
    CRITICAL: Agent "ork:security-auditor"        running for 1224min — may be hung.

    20 entries, all 1224–6823 minutes (20h–4.7d) old.
    Every entry is a zombie from a prior session.

## Before (watchdog.ts)

    for (const spawn of spawns) {
      if (spawn.agent_id === completedAgentId) continue;
      if (!spawn.timestamp) continue;

      const elapsed = now - new Date(spawn.timestamp).getTime();

      if (elapsed >= CRITICAL_MS) {
        warnings.push(`CRITICAL: ... ${min}min — may be hung.`);
      } else if (elapsed >= WARNING_MS) {
        warnings.push(`WARNING: ... ${min}min.`);
      }
    }

Every spawn ever, last 20 lines wide. Multi-session zombies stay forever.

## After (this PR)

    const MAX_SPAWN_AGE_MS = 24 * 60 * 60 * 1000;

    for (const spawn of spawns) {
      if (spawn.agent_id === completedAgentId) continue;
      if (!spawn.timestamp) continue;

      const elapsed = now - new Date(spawn.timestamp).getTime();
      if (elapsed > MAX_SPAWN_AGE_MS) continue;

      if (elapsed >= CRITICAL_MS) {
        warnings.push(`CRITICAL: ... ${min}min — may be hung.`);
      } else if (elapsed >= WARNING_MS) {
        warnings.push(`WARNING: ... ${min}min.`);
      }
    }

Anything older than 24h is silently reaped. Real recent hangs surface.

## After (same scenario)

    CRITICAL: Agent "agents-view-editor"          running for 6823min
    CRITICAL: Agent "manifest-reconciler"         running for 6822min
    CRITICAL: Agent "opus-sweeper"                running for 6822min
    ... 17 more zombies, all silently dropped ...

    (silent — no system message)

    Or, if a real recent hang exists alongside the zombies:
    CRITICAL: Agent "backend-system-architect" running for 11min — may be hung.

## Test coverage added

    describe('agentWatchdog', () => {
      // 8 existing tests preserved
      ...

      // New (#1882):
      it('silently drops zombie spawn older than 24h cap', ...);
      it('still warns for an agent JUST under the 24h cap', ...);
      it('only legitimate recent spawn surfaces when mixed with zombies', ...);
    });

    // 11/11 passing — verified locally with npx vitest