Problem
When the bot posts an inline reply on a PR, GitHub wraps it in a synthetic review object — empty body, state COMMENTED, authored by the bot. That object fires pull_request_review, which relays into a full tend-mention session. The session reads the whole PR, correctly concludes there is nothing to action, and exits. It is a guaranteed no-op: the review has no body, and its only inline comment is a reply the bot itself just wrote.
#828 named these objects and #835 taught the review skill's three review-record guards to ignore them. The tend-mention verify gate is a fourth site that still lets them through, because its empty-body skip is conditioned on approved:
if [ "$KIND" = "pull_request_review" ] \
&& [ "$REVIEW_AUTHOR" = "<<cfg.bot_name>>" ] \
&& [ "$REVIEW_STATE" = "approved" ] \
&& [ -z "$COMMENT_BODY" ]; then
generator/src/tend/templates/mention.yaml.j2#L246-L252. The comment above it explains the approved clause deliberately: "a bot CHANGES_REQUESTED/COMMENTED review keeps actionable signal". That is right for a real bot COMMENTED review — but a reply container is COMMENTED with an empty body and carries no signal at all, so it lands on the wrong side of the gate.
Occurrences
Four synthetic containers on max-sixty/cargo-affected across two windows, each one starting a relay run plus a dispatch session:
(The two 2026-08-04 rows are paired by submission order — both containers landed within a second of each other and both relay runs started within a second of each other, so the individual mapping is inferred rather than read off the payloads. The 2026-08-06 rows are exact: each dispatch session names its triggering review ID in the prompt.)
| 4872154023 | #77 | 2026-08-06T07:31:34Z | 31081259955 → 31081269852, $0.50 / 11 turns |
| 4872195438 | #77 | 2026-08-06T07:36:32Z | 31081583383 → 31081591885, $0.49 / 8 turns |
Both 2026-08-06 sessions reasoned their way to the right answer and posted nothing — 31081269852 wrote "The triggering review has an empty body and its only inline comment is a reply I authored as the PR author — not a request directed at me". That is the correct conclusion, reached at $0.99 for the pair, and it is reachable from the review record alone without starting a session. On a busy PR the multiplier grows: every inline reply the bot posts produces one.
Proposed fix
Skip when the review is a reply container rather than a submission. The review body being empty plus every attached inline comment being a bot-authored reply (in_reply_to_id != null) is a precise discriminator — it cannot match a real review, because a review that only replies to existing threads is not a review:
if [ "$KIND" = "pull_request_review" ] \
&& [ "$REVIEW_AUTHOR" = "<<cfg.bot_name>>" ] \
&& [ -z "$COMMENT_BODY" ] \
&& [ "$REVIEW_STATE" != "changes_requested" ]; then
# A submission has at least one non-reply inline comment; a synthetic reply
# container has none. `[] | all` is vacuously true, which also covers the
# empty-body APPROVED case the old gate handled.
if gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PAYLOAD_PR/reviews/$PAYLOAD_ID/comments" \
--jq '[.[] | .in_reply_to_id != null and (.user.login == "<<cfg.bot_name>>")] | all'; then
echo "should_run=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
This subsumes the current approved branch rather than sitting beside it, and preserves #166's actionable bot self-reviews: those carry either a body or standalone inline findings, and fail the all test either way. A human reply creates a container too, and user.login keeps that firing.
One caveat I could not settle from the API alone: the /reviews/{id}/comments call adds a request to every review-triggered verify job, on the path that currently short-circuits cheapest. The verify job already makes that exact call a few lines later for the @bot mention scan (L254-L262), so ordering the new check after it and reusing the response would avoid a second round-trip — I have not written that version, since the merge depends on how you would rather structure the gate.
Filed from tend-review-runs on max-sixty/cargo-affected under the standing cross-repo exception in that repo's running-tend overlay.
Problem
When the bot posts an inline reply on a PR, GitHub wraps it in a synthetic review object — empty
body, stateCOMMENTED, authored by the bot. That object firespull_request_review, which relays into a fulltend-mentionsession. The session reads the whole PR, correctly concludes there is nothing to action, and exits. It is a guaranteed no-op: the review has no body, and its only inline comment is a reply the bot itself just wrote.#828 named these objects and #835 taught the review skill's three review-record guards to ignore them. The
tend-mentionverify gate is a fourth site that still lets them through, because its empty-body skip is conditioned onapproved:generator/src/tend/templates/mention.yaml.j2#L246-L252. The comment above it explains theapprovedclause deliberately: "a bot CHANGES_REQUESTED/COMMENTED review keeps actionable signal". That is right for a real botCOMMENTEDreview — but a reply container isCOMMENTEDwith an empty body and carries no signal at all, so it lands on the wrong side of the gate.Occurrences
Four synthetic containers on max-sixty/cargo-affected across two windows, each one starting a relay run plus a dispatch session:
(The two 2026-08-04 rows are paired by submission order — both containers landed within a second of each other and both relay runs started within a second of each other, so the individual mapping is inferred rather than read off the payloads. The 2026-08-06 rows are exact: each dispatch session names its triggering review ID in the prompt.)
| 4872154023 | #77 | 2026-08-06T07:31:34Z | 31081259955 → 31081269852, $0.50 / 11 turns |
| 4872195438 | #77 | 2026-08-06T07:36:32Z | 31081583383 → 31081591885, $0.49 / 8 turns |
Both 2026-08-06 sessions reasoned their way to the right answer and posted nothing —
31081269852wrote "The triggering review has an empty body and its only inline comment is a reply I authored as the PR author — not a request directed at me". That is the correct conclusion, reached at $0.99 for the pair, and it is reachable from the review record alone without starting a session. On a busy PR the multiplier grows: every inline reply the bot posts produces one.Proposed fix
Skip when the review is a reply container rather than a submission. The review body being empty plus every attached inline comment being a bot-authored reply (
in_reply_to_id != null) is a precise discriminator — it cannot match a real review, because a review that only replies to existing threads is not a review:This subsumes the current
approvedbranch rather than sitting beside it, and preserves #166's actionable bot self-reviews: those carry either a body or standalone inline findings, and fail thealltest either way. A human reply creates a container too, anduser.loginkeeps that firing.One caveat I could not settle from the API alone: the
/reviews/{id}/commentscall adds a request to every review-triggered verify job, on the path that currently short-circuits cheapest. The verify job already makes that exact call a few lines later for the@botmention scan (L254-L262), so ordering the new check after it and reusing the response would avoid a second round-trip — I have not written that version, since the merge depends on how you would rather structure the gate.Filed from
tend-review-runson max-sixty/cargo-affected under the standing cross-repo exception in that repo'srunning-tendoverlay.