diff --git a/scripts/subagent_delivery_watchdog.mjs b/scripts/subagent_delivery_watchdog.mjs index 46b8908..81d5bd2 100755 --- a/scripts/subagent_delivery_watchdog.mjs +++ b/scripts/subagent_delivery_watchdog.mjs @@ -245,6 +245,10 @@ function decideRecoveryAction(payload, status) { ? attemptCountRaw : Number.parseInt(String(attemptCountRaw ?? '0'), 10); + if (!Number.isNaN(recoveryAttemptCount) && recoveryAttemptCount >= 2) { + return 'blocked'; + } + if (!Number.isNaN(recoveryAttemptCount) && recoveryAttemptCount >= 1) { return 'respawn'; } diff --git a/scripts/test_subagent_delivery_watchdog.mjs b/scripts/test_subagent_delivery_watchdog.mjs index f860977..b0c996e 100644 --- a/scripts/test_subagent_delivery_watchdog.mjs +++ b/scripts/test_subagent_delivery_watchdog.mjs @@ -268,6 +268,39 @@ ${result.stderr}`); } }); + +test('watchdog escalates to blocked when respawn recovery was already attempted and delivery is still not forwarded', () => { + const runner = createFixtureRunner(); + + try { + const inputPath = runner.writeFixture('dispatch-done-not-forwarded-blocked.json', { + runId: 'fixture-run-done-not-forwarded-blocked', + childSessionKey: 'session:done-not-forwarded-blocked', + dispatchAt: '2026-04-24T10:00:00.000Z', + expectedBy: '2026-04-24T10:10:00.000Z', + currentTime: '2026-04-24T10:07:00.000Z', + childRunStatus: 'done', + forwardedToMain: false, + recoveryAttemptCount: 2, + recoveryAction: 'respawn', + lastRecoveryAt: '2026-04-24T10:06:30.000Z', + }); + + const result = runner.runWatchdog(['--compact', '--input', inputPath]); + + assert.equal(result.status, 0, `expected zero exit status, got ${result.status} +${result.stderr}`); + assert.equal(result.stderr, ''); + + const payload = JSON.parse(result.stdout); + assert.equal(payload.ok, true); + assert.equal(payload.result.status, 'done_but_not_forwarded'); + assert.equal(payload.result.recoveryDecision, 'blocked'); + } finally { + runner.cleanup(); + } +}); + test('fixture runner exposes missing-input behavior for future fail-first cases', () => { const runner = createFixtureRunner();