Skip to content

tend-mention: bot's own non-empty-body review on someone else's PR starts a guaranteed no-op session (#747's sibling leg) #915

Description

@prql-bot

Problem

tend-mention starts a full billable session every time the bot leaves a non-empty-body review on a same-repo PR someone else authored. The session reads the PR, recognises the trigger as its own review, and exits silently. It is a guaranteed no-op: the bot is the reviewer, the findings are addressed to the PR's author, and the bot pushing unbidden to another author's branch is barred by running-in-ci's conduct rules — so there is no author role for it to act in.

This is the sibling leg of #747. That issue fixed the empty-body APPROVED case; the gate it added stops at body == "", on the reasoning max-sixty gave in the thread — "if it's an approval with nits then we may want to make follow-up changes?". That reasoning holds on a PR the bot authored (which is exactly what #166 and #761 protect), but not on someone else's PR, where the nits are instructions for the human author and the bot has nothing to follow up.

Distinct from #866 / #849, which target the synthetic empty-body COMMENTED container GitHub creates for an inline reply. These are real bot reviews with real bodies (455–1920 chars).

Evidence

Five occurrences in one 24h window on PRQL/prql (bot login prql-bot), covering every non-empty-body bot review left on a same-repo human PR in that window — there were no counter-examples:

Review PR (author) State / body Session started Cost Outcome
4889888509 #6169 (@max-sixty) APPROVED, 1567 chars 31280580245 $0.44 exited silently
4889928909 #6170 (@max-sixty) COMMENTED, 1453 chars 31281535113 $0.00 killed by a 429 before it could exit (see below)
4890641190 #6172 (@eitsupi) COMMENTED, 1503 chars 31297736617 $0.41 exited silently
4890671072 #6172 (@eitsupi) COMMENTED, 1920 chars 31298590569 $0.45 exited silently
4890681444 #6172 (@eitsupi) APPROVED, 455 chars 31298885324 $0.49 exited silently

$1.79 booked plus one run that never got to bill, out of a $31.52 window — and each also costs a relay run and a verify run.

The sessions say so themselves. From 31297736617: "The triggering review (ID 4890641190) was authored by prql-bot itself on eitsupi's PR #6172 — I'm the reviewer, not the author, and there are no replies or other comments on the thread. Responding to my own review would be a self-conversation loop, so I'm exiting silently without posting anything." From 31298590569: "I'm the reviewer, @eitsupi is the author, and the review's findings are addressed to them. … No comment, review, or push made."

The waste is not free. 31281535113 is the one that failed rather than exiting: its session JSONL ends on "error":"rate_limit", "apiErrorStatus":429, You've hit your session limit · resets 12am (UTC). The account's Claude session budget was exhausted at that moment, and one of the requests that had been drawing it down was this class of session. A no-op that can't run is still a no-op, but a no-op that consumes a scarce slot and then files a tend-outage issue is worse than one that just bills $0.45.

Root cause

For a pull_request_review event the gate falls through to the participation heuristic at mention.yaml.j2#L307-L313, which counts the triggering review itself, so BOT_REVIEWS is always non-empty and should_run=true is unconditional. The only skip on that path is the three-clause gate at L248-L254, whose approved + empty-body clauses exclude every review in the table above.

Proposed fix

The discriminator that's missing is the PR's author, and it's already resolved one line earlier. Insert between the PR_AUTHOR check (L301-L305) and the BOT_REVIEWS heuristic:

          # A review the bot leaves on someone else's PR has no author role to
          # follow up in — the findings are addressed to that PR's author, and
          # pushing unbidden to another author's branch is barred by
          # running-in-ci's conduct rules. The BOT_REVIEWS heuristic below
          # counts this very review, so without this the session always starts
          # and always exits silently (#747 fixed only the empty-body APPROVED
          # leg of the same shape). Placed *after* the PR_AUTHOR check above,
          # so #166/#761's actionable self-reviews on the bot's own PRs still
          # fire, and after the body/inline @-mention scans, so an explicit
          # summons still wins.
          if [ "$KIND" = "pull_request_review" ] \
             && [ "$REVIEW_AUTHOR" = "<<cfg.bot_name>>" ]; then
            echo "should_run=false" >> "$GITHUB_OUTPUT"; exit 0
          fi

At that point PR_AUTHOR != <bot> is already established, because the preceding block exit 0s when they're equal — so this cannot swallow the bot-authored-PR case. Everything the earlier gates admit is unaffected: a body @-mention returns at L220-L224, an inline-comment @-mention at L260-L267, and a human's review never reaches here with REVIEW_AUTHOR set to the bot.

Scope note: only same-repo PRs are affected. The relay job's head.repo.full_name == github.repository condition means bot reviews on fork PRs never dispatch — verified against the same window, where three non-empty-body bot reviews on @kgutwin's fork PRs (#6155, #6156, #6162) started no session at all.

Worth confirming against the control in the same window: 31300648824, triggered by the bot's own COMMENTED review on #6173 — a bot-authored PR — is the case that must keep firing, and the proposed gate leaves it firing.

How the occurrences were enumerated

Every bot review submitted in the window was listed with state and body length, cross-referenced against the PR's head.repo.full_name and author, then matched to the repository_dispatch run it produced; each run's session artifact was downloaded and its final assistant turn read to confirm the exit was a no-op and that no comment, review, or push was made. The two empty-body APPROVED reviews in the same window (4890133352 on #6170, 4889810454 on #6168) correctly produced no session — #747's gate working — which is what makes the body-length clause the load-bearing one.

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