---
name: a-terminal-exit-watcher-is-blind-to-a-rerun-of-the-same-run
description: "A watcher that exits on run-terminal is SATISFIED by the dead attempt it was armed against — bind the exit condition to run_attempt, not status, or a same-sha rerun runs unwatched"
metadata:
  node_type: memory
  type: feedback
  originSessionId: 67eaf193-4427-47cd-bf9b-94b9db7bb25a
  modified: 2026-09-09T05:49:18.210Z
---

A GitHub Actions **rerun keeps the run id**. So a watcher whose exit condition is `status == completed` has already been satisfied by the attempt that just went red, and it exits *before* the rerun it exists to observe. Re-arming without changing the predicate rebuilds the same blindness: the new watcher reads `completed` on its first poll — the DEAD attempt's record — and exits within seconds, printing a clean, confident terminal line about a run that is at that moment starting over.

**Bind the exit to the attempt, not the status:** `completed AND run_attempt >= <the attempt you dispatched>`. Read `attempt` in the same call as `status`, and read the JOB list at `.../attempts/<attempt>/jobs`, because the default job endpoint mixes carried rows in.

**Why:** hit twice on the v0.68.0 golden drive. r2: Monitor `b8cgrp5d4` exited on run-terminal by design and would not have seen the `--failed` rerun at all — caught only because the gap was audited afterwards, and a fresh Monitor was armed. r3 (2026-09-09, doyle's LEDGERED-CLASS ruling, run 34310511612 attempt 2): the predicate was written as `attempt >= 2` from the start, which is the only reason the attempt was watched from its first minute. The failure mode is silent in the direction that does not announce itself — you do not get "watcher died", you get a *plausible verdict about the wrong attempt*, which is worse than no watcher at all. Sibling of [[audit-live-watchers-before-rearming]] (whose subject is a watcher that outlives its ruling; this one is a watcher that dies before its subject) and of [[rerun-carried-jobs-dedupe-executions]] (same root cause: the rerun reuses the run id and carries rows forward, so every run-level reading is ambiguous between attempts).

**How to apply:** after ANY rerun dispatch, (1) re-arm the watch, never assume the old one carried; (2) make the exit predicate name the attempt number you just dispatched; (3) verify the attempt advanced *in the dispatching script itself* (`attempt == 2`, `headSha` unchanged) rather than in a later read — a later read cannot distinguish "rerun took" from "I am looking at the old attempt"; (4) log EVERY poll with the observed values, per [[audit-live-watchers-before-rearming]], so a stale last line is itself proof of death. Kin: [[a-stale-target-makes-a-check-pass-harder-not-fail]] — a dead run passes a liveness gate harder than a live one, and this is the same trap wearing a watcher's clothes.
