Skip to content

nightly closes a PR out from under the mention session that just fixed it — divergent dispositions on the same review (#865 sibling) #943

Description

@prql-bot

Problem

A sibling symptom of #865, but a different failure: the two live sessions don't duplicate each other's work — they take divergent dispositions on the same review, and the PR-holding session's disposition silently voids the other's completed, pushed, CI-green work. The review offered two remedies; each session picked a different one; the one that ran last won, deleted the branch, and never learned the other had already acted.

Observed on PRQL/prql#6182:

Time (UTC) Actor Event
06:35:40 tend-nightly 31465658903 session starts
06:54:15 nightly pushes a1c983e5, opens #6182 (a CHANGELOG follow-up), stays live polling its CI
06:58:33 tend-review 31466828345 posts review 4903695064 — the entry's closing sentence calls the change breaking, which is false since enum is unreleased. Offers two remedies: drop that one sentence, or close the PR
06:59:01 tend-mention 31467110614 dispatched on that review — nightly is still live
07:01:39 mention picks remedy 1: pushes 58a027f9 dropping the sentence, rewrites the PR body, replies to inline comment 3755865403
07:08:11 mention polls CI to terminal green (9 checks SUCCESS, 21 skipped), exits reporting "PR is open and awaiting a maintainer"
07:09:21 nightly picks remedy 2: comments "Dropping only the breaking-change sentence … would leave an accurate but redundant entry" — future tense, because it doesn't know it's already been done
07:09:22 nightly gh pr close 6182 --delete-branch
07:10:39 nightly exits

docs/changelog-enum-duplicates now 404s. 58a027f9 survives only through the PR ref.

Why the sessions couldn't see each other

The mention run had no way to know nightly was still alive — that's #865's mechanism and I'm not re-reporting it. The new part is what the PR-holding session did before a terminal action. Its entire re-read of #6182 before closing was one call:

gh pr view 6182 --json reviews --jq '.reviews[] | select(.body != "") | .body'

Review bodies only. No commits, no headRefOid, no comments, no inline-comment fetch. Every one of those would have shown 58a027f9, the inline reply posted at 07:01:55, or the rewritten body. So the sibling's work was invisible by construction, not by bad luck — the same query replays identically every time.

running-in-ci's Recheck Before Posting is the rule that would have caught it, and it very nearly says this already: "Treating the task as done is itself a kind of post: re-fetch before ending the turn, not only before commenting." But every re-fetch recipe under that heading counts comments and reviews — the entities a duplicate post would collide with. Closing a PR collides with commits and branch state instead, and nothing tells a session to re-read those. The --delete-branch makes it destructive rather than merely wasteful.

Re-check PR state before pushing a follow-up commit has the right instinct — "any wait that lets time pass … also gives a maintainer time to merge or close the PR" — but it's scoped to pushes, and its check is state == OPEN, which was true here. The symmetric case (a wait gives a sibling session time to push, and you're about to close) isn't covered.

Bearing on #870

This is the part worth weighing before #870 lands. Its rule — a session that opens a PR and stays alive polling it leaves a review that lands mid-poll to tend-mention — would have made nightly stand down entirely. Applied here, #6182 would still be open today carrying a redundant CHANGELOG entry, because closing it was the better call and nightly was the only actor that made it. The mention session, holding author role, did what author role does: it applied the fix. It had no standing to conclude the PR shouldn't exist.

So neither full stand-down nor the status quo is right for this leg. The PR-holder needs to keep the authority to withdraw a PR whose premise the review invalidated, but re-read what changed since its own last write before exercising it. Concretely, before a terminal action on a PR the session opened:

# Anything the session didn't write itself means a sibling is live on this PR.
gh pr view "$N" --json headRefOid,commits,comments,reviews \
  --jq '{head: .headRefOid, commits: [.commits[].oid], comments: (.comments|length)}'

If the head has moved past the SHA this session pushed, a sibling has acted — reconcile with what it did rather than closing over it. In this case the reconciliation is cheap and the outcome is arguably better than either session's solo answer: the sibling had already made the entry accurate, so nightly's close comment could have said "a sibling already dropped the sentence; closing anyway because the accurate entry is still redundant" and the maintainer would see one coherent story instead of two contradictory ones.

I'd also note the smaller, separable half: the mention session's final report — "PR is open and awaiting a maintainer" — was false 71 seconds after it was written, and it's the only durable record that session leaves. Nothing asks a session to re-verify a state claim it makes in its closing summary. That may be worth a sentence wherever End the turn only when work is shipped lives, independent of whatever #870 settles on.

Evidence

  • Nightly session log: run 31465658903, artifact claude-session-logs-0adf0aef. The gh pr view 6182 --json reviews call and the gh pr close 6182 --comment … --delete-branch immediately after it are adjacent Bash tool calls with nothing between them.
  • Mention session log: run 31467110614, artifact claude-session-logs-c32c1c0b — $0.94 / 23 turns / 9m10s, ending "PR is open and awaiting a maintainer."
  • Review that offered both remedies: run 31466828345, $1.53.
  • docs/changelog-enum-duplicates returns 404 from repos/PRQL/prql/git/refs/heads/…; 58a027f9 carries 9 SUCCESS / 21 skipped check-runs.

Third occurrence of the "multiple tend sessions live on one PR" family on PRQL/prql (2026-08-04 and 2026-08-06 are recorded in that repo's review-runs tracker; the 08-06 one lost a test transiently and recovered it). This is the first where the loss wasn't recovered.

Filed from tend-review-runs on PRQL/prql under the standing cross-repo exception in that repo's running-tend overlay.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions