From 6ad1e1960fe96d3b1e7617442c56a64609eb1973 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:56:05 +0000 Subject: [PATCH 1/4] skills(review-reviewers): require a named non-bot actor before calling output accepted --- .../skills/review-reviewers/SKILL.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md index b9af0f82..7cb1d979 100644 --- a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md @@ -228,6 +228,13 @@ Use a cheap subagent (e.g. Haiku / gpt-mini) and a prompt like: > 1. Did the bot produce visible output (review, comment, issue action, commit)? > 2. If yes, was the output accepted or rejected? > +> **Acceptance needs a non-bot actor — name it.** For every acceptance or rejection signal, report the login that produced it: who merged, who replied, who pushed the follow-up. `$BOT_LOGIN` reviewing, replying to, or pushing to its own PR is the bot talking to itself, not acceptance. List the thread's distinct participants before calling anything accepted, and report a bot-only thread as `bot-only — no human signal`: +> +> ```bash +> gh api "repos/$ARGUMENTS/pulls//comments?per_page=100" --jq '[.[].user.login] | unique' +> gh api "repos/$ARGUMENTS/issues//comments?per_page=100" --jq '[.[].user.login] | unique' +> ``` +> > **How to map runs to outputs:** > - `tend-review`: `gh -R $ARGUMENTS run view --json headBranch` → find PR via > `gh -R $ARGUMENTS pr list --head --state all` → check bot reviews via @@ -294,16 +301,19 @@ Use a cheap subagent (e.g. Haiku / gpt-mini) and a prompt like: > - : (e.g., "no artifacts", "notification no-op") > > ## Runs with accepted output -> - : on PR #N — bot reviewed, PR merged +> - : on PR #N — bot reviewed, PR merged by +> +> ## Runs with bot-only threads (no human signal) +> - : on PR #N — participants: [, ...] > > ## Runs with concerning output -> - : on PR #N — (e.g., "human posted CHANGES_REQUESTED") +> - : on PR #N — by (e.g., "human posted CHANGES_REQUESTED") > > ## Sanity check > > ``` -Review the subagent's summary. If all outputs are accepted and no sanity-check flags, skip to Step 6 (summary). If concerning outcomes exist, continue to Step 3. +Review the subagent's summary, and verify any actor attribution before it enters a finding — a survey that credits the bot's own reply to a human turns a self-conversation into a false all-clear. Bot-only threads are neither accepted nor concerning on their own; judge them on content. If all outputs are accepted and no sanity-check flags, skip to Step 6 (summary). If concerning outcomes exist, continue to Step 3. ## Step 3: Investigate concerning outcomes via cheap subagent From 32dee42b40234f84a49a09c816ec7bc45ae84b91 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:06:01 +0000 Subject: [PATCH 2/4] skills(review-reviewers): query reviews and the merge actor, not just comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two comments endpoints can't produce the login the report format asks for: /pulls//comments carries only inline review comments and /issues//comments only conversation comments. Neither reaches review records or the merge actor, so the most common acceptance shape here — a maintainer merging a bot PR without commenting — came back as bot-only, and a human CHANGES_REQUESTED with no inline comments was invisible too. Add the merge-actor, reviews, and commits queries, and give the bot-only bucket a branch in the routing sentence. --- plugins/tend-ci-runner/skills/review-reviewers/SKILL.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md index 7cb1d979..47c075d5 100644 --- a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md @@ -228,13 +228,18 @@ Use a cheap subagent (e.g. Haiku / gpt-mini) and a prompt like: > 1. Did the bot produce visible output (review, comment, issue action, commit)? > 2. If yes, was the output accepted or rejected? > -> **Acceptance needs a non-bot actor — name it.** For every acceptance or rejection signal, report the login that produced it: who merged, who replied, who pushed the follow-up. `$BOT_LOGIN` reviewing, replying to, or pushing to its own PR is the bot talking to itself, not acceptance. List the thread's distinct participants before calling anything accepted, and report a bot-only thread as `bot-only — no human signal`: +> **Acceptance needs a non-bot actor — name it.** For every acceptance or rejection signal, report the login that produced it: who merged, who reviewed, who replied, who pushed the follow-up. `$BOT_LOGIN` reviewing, replying to, or pushing to its own PR is the bot talking to itself, not acceptance. A non-bot merge *is* acceptance even when every commenter is the bot — the most common shape here is a maintainer merging a bot PR without ever posting. Check all five surfaces before calling a thread accepted or bot-only, and reserve `bot-only — no human signal` for threads where `$BOT_LOGIN` is the only login across all of them: > > ```bash +> gh api "repos/$ARGUMENTS/pulls/" --jq '{merged_by: .merged_by.login, merged_at: .merged_at}' +> gh api "repos/$ARGUMENTS/pulls//reviews?per_page=100" --jq '[.[] | {login: .user.login, state}] | unique' > gh api "repos/$ARGUMENTS/pulls//comments?per_page=100" --jq '[.[].user.login] | unique' > gh api "repos/$ARGUMENTS/issues//comments?per_page=100" --jq '[.[].user.login] | unique' +> gh api "repos/$ARGUMENTS/pulls//commits?per_page=100" --jq '[.[].author.login] | unique' > ``` > +> The two comments endpoints alone are not enough: `/pulls//comments` returns only inline review comments and `/issues//comments` only conversation comments. Neither carries review records or the merge actor, so a silent maintainer merge and a human `CHANGES_REQUESTED` with no inline comments are both invisible without the first two queries. +> > **How to map runs to outputs:** > - `tend-review`: `gh -R $ARGUMENTS run view --json headBranch` → find PR via > `gh -R $ARGUMENTS pr list --head --state all` → check bot reviews via @@ -313,7 +318,7 @@ Use a cheap subagent (e.g. Haiku / gpt-mini) and a prompt like: > > ``` -Review the subagent's summary, and verify any actor attribution before it enters a finding — a survey that credits the bot's own reply to a human turns a self-conversation into a false all-clear. Bot-only threads are neither accepted nor concerning on their own; judge them on content. If all outputs are accepted and no sanity-check flags, skip to Step 6 (summary). If concerning outcomes exist, continue to Step 3. +Review the subagent's summary, and verify any actor attribution before it enters a finding — a survey that credits the bot's own reply to a human turns a self-conversation into a false all-clear. Route on the buckets: if concerning outcomes exist, continue to Step 3. Otherwise judge the bot-only threads on their content — a self-review chain that went wrong is a finding even with no human in it, and one that read fine is not — and if nothing there concerns you and there are no sanity-check flags, skip to Step 6 (summary). ## Step 3: Investigate concerning outcomes via cheap subagent From 04d9671ddab0b325cf6efc2ae92b471cdbc9c725 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:50:58 +0000 Subject: [PATCH 3/4] skills(review-reviewers): collapse the actor sweep into one gh pr view --- .../skills/review-reviewers/SKILL.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md index e47864b6..c8de8128 100644 --- a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md @@ -252,17 +252,20 @@ Use a cheap subagent (e.g. Haiku / gpt-mini) and a prompt like: > > The comment endpoints cover conversation and inline-review comments only; neither returns review submissions, and an empty-body `APPROVE` is `tend-review`'s most common output. Without the fourth count an approvals-only window reports `0, 0` and satisfies the gate below with two zeros carrying no signal. Report all four numbers at the top of your summary. If the sweep found in-window rows your per-run walk did not reach, the walk is wrong — re-map from the PR numbers the sweep found rather than reporting those runs as silent. > -> **Acceptance needs a non-bot actor — name it.** For every acceptance or rejection signal, report the login that produced it: who merged, who reviewed, who replied, who pushed the follow-up. `$BOT_LOGIN` reviewing, replying to, or pushing to its own PR is the bot talking to itself, not acceptance. A non-bot merge *is* acceptance even when every commenter is the bot — the most common shape here is a maintainer merging a bot PR without ever posting. Check all five surfaces before calling a thread accepted or bot-only, and reserve `bot-only — no human signal` for threads where `$BOT_LOGIN` is the only login across all of them: +> **Acceptance needs a non-bot actor — name it.** For every acceptance or rejection signal, report the login that produced it: who merged, who reviewed, who replied, who pushed the follow-up. `$BOT_LOGIN` reviewing, replying to, or pushing to its own PR is the bot talking to itself, not acceptance. A non-bot merge *is* acceptance even when every commenter is the bot — the most common shape here is a maintainer merging a bot PR without ever posting. Reserve `bot-only — no human signal` for threads where `$BOT_LOGIN` is the only login across every surface. One `gh pr view` covers them all — merge actor, reviews, inline comments, conversation comments, commits: > > ```bash -> gh api "repos/$ARGUMENTS/pulls/" --jq '{merged_by: .merged_by.login, merged_at: .merged_at}' -> gh api "repos/$ARGUMENTS/pulls//reviews?per_page=100" --jq '[.[] | {login: .user.login, state}] | unique' -> gh api "repos/$ARGUMENTS/pulls//comments?per_page=100" --jq '[.[].user.login] | unique' -> gh api "repos/$ARGUMENTS/issues//comments?per_page=100" --jq '[.[].user.login] | unique' -> gh api "repos/$ARGUMENTS/pulls//commits?per_page=100" --jq '[.[].author.login] | unique' +> gh -R $ARGUMENTS pr view --json number,state,author,mergedBy,reviews,comments,commits --jq '{ +> pr: .number, state, author: .author.login, merged_by: .mergedBy.login, +> reviews: [.reviews[] | {login: .author.login, state}], +> logins: ([.mergedBy.login, .reviews[].author.login, .comments[].author.login, +> .commits[].authors[].login] | map(select(. != null and . != "")) | unique) +> }' > ``` > -> The two comments endpoints alone are not enough: `/pulls//comments` returns only inline review comments and `/issues//comments` only conversation comments. Neither carries review records or the merge actor, so a silent maintainer merge and a human `CHANGES_REQUESTED` with no inline comments are both invisible without the first two queries. +> `logins` is the bot-only test: `["$BOT_LOGIN"]` or empty means no human touched the thread. Anything else, name that login and say which surface it came from. Every inline review comment belongs to a review record — including a standalone reply posted through the replies endpoint — so `reviews` covers inline commenters as well as submitted reviews, and its `state` gives the accept/reject direction. `comments`, `reviews`, and `commits` paginate in full, so a long thread does not truncate. +> +> A comments-only check is not enough on its own: it misses both a silent maintainer merge (`mergedBy` set, nobody commenting) and a human `CHANGES_REQUESTED` that carries no inline comments. > > **How to map runs to outputs:** > - `tend-review`: `gh -R $ARGUMENTS run view --json headBranch` → find PR via From 730011c0f8bdd5fa5213e1b5eab86a82073d1301 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:59:51 +0000 Subject: [PATCH 4/4] skills(review-reviewers): note that gh pr view truncates commits at 100 --- plugins/tend-ci-runner/skills/review-reviewers/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md index c8de8128..7b157e45 100644 --- a/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-reviewers/SKILL.md @@ -263,7 +263,7 @@ Use a cheap subagent (e.g. Haiku / gpt-mini) and a prompt like: > }' > ``` > -> `logins` is the bot-only test: `["$BOT_LOGIN"]` or empty means no human touched the thread. Anything else, name that login and say which surface it came from. Every inline review comment belongs to a review record — including a standalone reply posted through the replies endpoint — so `reviews` covers inline commenters as well as submitted reviews, and its `state` gives the accept/reject direction. `comments`, `reviews`, and `commits` paginate in full, so a long thread does not truncate. +> `logins` is the bot-only test: `["$BOT_LOGIN"]` or empty means no human touched the thread. Anything else, name that login and say which surface it came from. Every inline review comment belongs to a review record — including a standalone reply posted through the replies endpoint — so `reviews` covers inline commenters as well as submitted reviews, and its `state` gives the accept/reject direction. `comments` and `reviews` paginate in full. `commits` does not: `gh pr view` selects `commits(first: 100)` and never fetches a second page, oldest-first, so past 100 commits the newest drop out — exactly where a human follow-up push sits. On a PR that long, read the authors from `gh api "repos/$ARGUMENTS/pulls//commits" --paginate` (itself capped at 250) before calling the thread bot-only. > > A comments-only check is not enough on its own: it misses both a silent maintainer merge (`mergedBy` set, nobody commenting) and a human `CHANGES_REQUESTED` that carries no inline comments. >