watchpr: detect merge/close in poll loop (was hanging after baseline) #3

Merged
benvin merged 1 commits from benvin/watchpr-poll-loop-fix into main 2026-08-12 23:40:50 +10:00
Member

Why

In production, watchpr --interval 60s unkin/artifactapi#118 (single PR) printed baseline established and then never exited even after the PR was merged -- the process stayed alive but never detected the merge. watchpr --once reported the same PR correctly as merged, and an earlier two-PR watch had correctly detected a merge, so the single-shot fetch and change logic were fine; the watch loop was not.

Root cause: the poll loop only reported a transition from the baseline snapshot. MeaningfulChange detects merge/close only across an edge (!prev.Merged && cur.Merged, prev.State=="open" && cur.State=="closed"). A PR already merged or closed when watchpr starts is captured as the baseline, so no transition ever occurs and the loop polls the dead PR forever.

How

  • Add a terminal-state check applied to the baseline snapshot: a PR already merged or closed the moment watchpr starts is reported and exits immediately (it can never change again), instead of being polled forever.
  • Extract the baseline + poll loop into agent.Watch behind a StateFetcher interface, so the loop, its open->merged/close detection, and its poll-error resilience are unit-testable with a fake client.
  • Preserve existing behaviour: poll errors are logged and the loop continues; only a baseline fetch error aborts.
  • Add table/loop tests, including one that hangs (and fails on timeout) against the pre-fix code: an already-merged PR at baseline must exit with PR merged without consuming a tick.
## Why In production, `watchpr --interval 60s unkin/artifactapi#118` (single PR) printed `baseline established` and then never exited even after the PR was merged -- the process stayed alive but never detected the merge. `watchpr --once` reported the same PR correctly as merged, and an earlier two-PR watch had correctly detected a merge, so the single-shot fetch and change logic were fine; the watch loop was not. Root cause: the poll loop only reported a *transition* from the baseline snapshot. `MeaningfulChange` detects merge/close only across an edge (`!prev.Merged && cur.Merged`, `prev.State=="open" && cur.State=="closed"`). A PR already merged or closed when watchpr starts is captured as the baseline, so no transition ever occurs and the loop polls the dead PR forever. ## How - Add a terminal-state check applied to the baseline snapshot: a PR already merged or closed the moment watchpr starts is reported and exits immediately (it can never change again), instead of being polled forever. - Extract the baseline + poll loop into `agent.Watch` behind a `StateFetcher` interface, so the loop, its open->merged/close detection, and its poll-error resilience are unit-testable with a fake client. - Preserve existing behaviour: poll errors are logged and the loop continues; only a baseline fetch error aborts. - Add table/loop tests, including one that hangs (and fails on timeout) against the pre-fix code: an already-merged PR at baseline must exit with `PR merged` without consuming a tick.
unkin-agent added 1 commit 2026-08-12 23:33:14 +10:00
watchpr: detect merge/close in poll loop (was hanging after baseline)
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
f915b5ba3b
watchpr only reported meaningful changes as transitions from the poll
baseline. A PR already merged or closed when the watch started was
captured as the baseline and never produced a transition, so the loop
polled the dead PR forever (process alive, never exiting) -- the
single-PR --interval case observed in production.

Add a terminal-state check applied to the baseline snapshot: a PR that
is already merged or closed the moment watchpr starts is reported and
exits immediately, since it can never change again. Extract the
baseline+poll loop into agent.Watch behind a StateFetcher interface so
the loop, its open->merged/close detection, and its poll-error
resilience are unit-testable with a fake client.
benvin merged commit c6712063bc into main 2026-08-12 23:40:50 +10:00
benvin deleted branch benvin/watchpr-poll-loop-fix 2026-08-12 23:40:51 +10:00
Sign in to join this conversation.