docs: name the conflict watchpr will never report
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

This commit is contained in:
2026-09-26 21:14:20 +10:00
parent 15c527ee56
commit 91509cb9b2
2 changed files with 28 additions and 11 deletions
+16 -7
View File
@@ -224,18 +224,27 @@ wrapped per stage (login / read denied / write denied) via `ErrVaultDenied`.
read, so a PR that is already conflicted or already CI-failing when watching read, so a PR that is already conflicted or already CI-failing when watching
starts is polled on rather than reported. That suppression is printed with the starts is polled on rather than reported. That suppression is printed with the
baseline, since a silent watcher and a watcher with nothing to say look alike. baseline, since a silent watcher and a watcher with nothing to say look alike.
The baseline goes to stderr in both modes — a JSON record under `--json` —
because automation is what most needs it and stdout carries the result.
- Mergeability is the one `watchpr` rule needing a run of observations - Mergeability is the one `watchpr` rule needing a run of observations
(`prWatch`). Gitea 1.26 sends `mergeable` as a plain bool — always present, (`prWatch`). Gitea 1.26 sends `mergeable` as a plain bool — always present,
never null — and sends `false` both for a real conflict and while it recomputes never null — and sends `false` both for a real conflict and while it recomputes
the merge base after a push, which is exactly when an agent starts a watch. So the merge base after a push, which is exactly when an agent starts a watch. So
`false` counts only once this watch has seen a merge computation start (the PR `false` counts only once this watch has seen a merge computation start (the PR
was mergeable, or `head.sha`/`base.sha` moved), and then only across two was mergeable, or `head.sha`/`base.sha` moved), and then only once the run of
consecutive polls of an unchanged head and base; a mergeable poll, an unknown `false`s has spanned `conflictWindow` (2m); a mergeable poll, an unknown one or
one or a failed poll all break the run. `base.sha` is the base branch tip as of a failed poll all break the run. The debounce is wall-clock, measured from the
the response, not the merge base at PR creation, so it moves when main does. tick that fired each poll, because what it outlasts is the recompute and
`Mergeability` stays tri-state for what the bool cannot carry: an absent/null `--interval` spans seconds to hours. Arming and the run are independent:
flag from some other Gitea, and a state no successful poll ever filled in. movement arms and nothing else, since `base.sha` is the base branch tip as of
Head and base SHAs are read only to arm this rule — a push never alerts. the response, so it moves for every open PR whenever main does and a run reset
there could never complete on a busy repo. `Mergeability` stays tri-state for
what the bool cannot carry: an absent/null flag from some other Gitea, and a
state no successful poll ever filled in. Head and base SHAs are read only to
arm — a push never alerts. The residue: a conflict landed by the push just
before the watch began, on a head and base that never move again, is never
reported; Gitea's payload has no field separating it from a check in flight
(`merge_base` is the true merge base and does not move on recheck).
- CI "combined status" comes from `/commits/{sha}/status`; an empty head SHA - CI "combined status" comes from `/commits/{sha}/status`; an empty head SHA
yields an empty state without an API call. yields an empty state without an API call.
- Gitea backs every PR with an issue of the same number and serves comments from - Gitea backs every PR with an issue of the same number and serves comments from
+12 -4
View File
@@ -88,13 +88,21 @@ gets a **new comment from someone other than the agent**, its **CI fails**
transitions — CI `pending`→`success`, the agent's own comments and pushes — are transitions — CI `pending`→`success`, the agent's own comments and pushes — are
ignored, and so is any condition the PR was already in at the baseline: a PR ignored, and so is any condition the PR was already in at the baseline: a PR
that is already conflicted or already failing when watching starts keeps being that is already conflicted or already failing when watching starts keeps being
watched, and the baseline line says which conditions it is staying silent about. watched, and the baseline line — written to stderr, as a JSON record under
`--json` — says which conditions it is staying silent about.
Gitea reports `mergeable: false` both for a real conflict and while it Gitea reports `mergeable: false` both for a real conflict and while it
recomputes the merge base after a push, so a conflict is only reported once the recomputes the merge base after a push, so a conflict is only reported once the
watch has seen a merge computation start — the PR was mergeable, or its head or watch has seen a merge computation start (the PR was mergeable, or its head or
base commit moved — and then only across two consecutive polls of an unchanged base commit moved) **and** the non-mergeable polls have then run unbroken for
head and base. two minutes. The debounce is a duration, not a poll count, because what it has
to outlast is Gitea's recompute and `--interval` ranges from seconds to hours.
One case is therefore never reported: a conflict introduced by the push
immediately before the watch started, on a PR whose head and base never move
again. Nothing in Gitea's payload separates that from a merge check still in
flight, so watchpr stays silent about it for as long as it runs — the baseline
line is how you see it.
```bash ```bash
# Watch until something meaningful happens (default interval 60s) # Watch until something meaningful happens (default interval 60s)