watchpr: only alert on mergeability lost after the baseline #20
Reference in New Issue
Block a user
Delete Branch "benvin/watchpr-baseline-from-first-poll"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
watchpr exited on its first poll for any PR already conflicted when watching began. Gitea sends
mergeable: falseboth for a real conflict and while it recomputes the merge base after a push, sofalsealone decides nothing.falses must then span 2m of wall clock, because--intervalranges from seconds to hours--jsonMergeNobaseline disarms the conflict rule for the whole run, but Gitea also returnsmergeable=falsewhile it recomputes the merge base after a push (the rationale at watch.go:144), which is exactly when watchpr is started; if that push introduced a real conflict, every later poll readsMergeNo,everMergeablenever becomes true, and the conflict is never reported (checked: non-mergeable baseline + 100 conflicted polls, no alert) -> re-arm on aHeadSHAchange, or take the mergeability baseline from a settled read rather than the single baseline poll.watchpr --interval 5s: one line, no state) -> print each ref's baseline state so the condition being deliberately ignored is visible.mergeableis tri-state (absent/null = not yet computed)" does not hold on Gitea 1.26.2: the field is a plain bool and is always present (every payload for this repo's PRs 1-20 carriestrue/false), and the not-yet-computed value isfalse, soMergeUnknownis unreachable and the stated reason for the tri-state is wrong -> correct the claim; keep the decoding as defensive if you like, but do not document null as the recompute signal.conflictsis reset only byMergeYes, never byMergeUnknownand never by a failed poll, so two non-adjacentMergeNoobservations satisfy a rule documented as "consecutive" (No+ 50Unknown+Noalerts on poll 52) -> reset the run on an unknown or failed observation.falseis folded into the run:mergeInputsChangedzeroesconflicts, thentrack(cur)sets it back to 1 on that same snapshot, so a head or base move plus one further poll fires. Gitea returnsmergeable=falsefor the whole post-push/post-base-move recheck (live: argocd-apps#172 readfalsethentrueseconds apart with head3d85105aand base53412535both unchanged, right after main moved to53412535), so an armed watch at an interval shorter than that window reportsPR lost mergeability (conflict)on a PR that is mergeable — contradicting cmd/watchpr/main.go:5 "the agent's own pushes ... are ignored" -> do not count the arming poll's own observation toward the run, or debounce on elapsed time rather than a poll count.base.shais the base branch tip, so it moves for every open PR on every push to main (verified: all open argocd-apps PRs report the samebase.sha, tracking the tip). The branch that arms also resetsconflicts, so where the base moves faster than--intervala genuine conflict never completes a run (armed watch, 20 conflicted polls with base moving each poll: no alert) -> arm on merge-input movement without resetting the run of consecutivefalseobservations.--jsondrops the baseline entirely, so the only notice that the watch is deliberately silent about a pre-existing conflict is missing in the mode automation uses; the baseline goes to stderr and cannot corrupt the stdout stream. README.md:91 states the baseline line unconditionally -> emit the baseline as a JSON record on stderr under--json.MergeNobaseline on the already-pushed head, 50 stable polls, no alert) -> describe what is actually scripted.falses accumulates while the watch is still disarmed, andobservearms and then evaluatesconfirmed(now)against that pre-armingconflictSince, so the first head/base move confirms a run it played no part in, on its own recompute poll. BaselineMergeNo+ three stableMergeNopolls at 60s + one poll whosebase.shamoved alertsPR lost mergeability (conflict)on that fourth poll (verified throughWatchwithspacedTicks); same withhead.shamoving, which contradicts cmd/watchpr/main.go:5 and AGENTS.md:244 "a push never alerts". On this Giteabase.shais the base-branch tip (live: every open argocd-apps PR reportsbase.sha= main's tip426a399f), so this fires on any watch baselined non-mergeable as soon as anyone merges to main -> start the window at the later of the run's first observation and thearmedfalse→true transition, and add the sequence toTestWatchConflictSequences, which has no case where falses precede the arming move.base.shais the base-branch tip, so its "head and base never move again" precondition ends at the next push to main -> state the rule the code implements, and say the gap closes when the head or base next moves.--jsonstderr carries NDJSON baseline records interleaved with plain-textwarning:lines, so stderr is not parseable as a stream; a caller that reads it as NDJSON breaks on the first poll error or a missing token -> emit the warnings as records too under--json, or document stderr as human-readable with JSON records embedded.time.Timeis both "no run in progress" and a legal clock value, so atickschannel delivering zero times never confirms a conflict however long it runs (verified: 50 conflicted polls, no alert) -> track the run with an explicit flag, or state the non-zero-clock requirement onWatch's doc comment.suppressedAtBaselinenamesMergeNoand CI red, but aMergeUnknownbaseline is equally disarmed and produces no notice -> report it too.