.agents/skills/pr-reviews/SKILL.md
This skill iterates a PR through review/comment cycles until there is nothing left to address.
When this skill is in use, the agent's job is to bring the PR into merge-ready shape: solve the original problem the PR was opened for, and address every legitimate finding the PR has accumulated, from every source. Reviewers, linters, and CI all matter. Comments are the loudest source but they are not the only source -- you must proactively pull findings from every channel that reports on the PR, not wait for something to surface as a chat message.
Sources of findings, in priority order:
A finding is "relevant to this PR" if its existence (or its line location) is plausibly caused by the PR's diff. CI failures unrelated to this PR (a flaky test on an unrelated module, an infra outage) are NOT in scope -- note them, surface to the user at the end, do not fix them here.
The bar is the project's performance, stability, and long-term maintainability. Don't dismiss findings because they look minor.
These are non-negotiable. Skipping any of them will cost the user time.
page=N+1 request. fetch-all.sh does this automatically.ci-status.sh. If there are FAILURES, fix
them and bundle into the same push. If checks are still running,
that's fine -- ignore them and push anyway. The next push triggers
fresh CI on the new code, which is what we actually care about.wait-for-activity.sh
for new comments (NOT for CI).wait-for-activity.sh times out (30 min, no new comments):
re-check ci-status.sh. If checks are still running, that's normal,
surface to the user. If there are failures, fix and iterate.trigger-cubic.sh).trigger-coderabbit.sh; @coderabbitai review is incremental,
full review re-reads the whole PR).trigger-copilot.sh is kept for the case where credits exist,
but it is not part of the loop and Copilot never blocks the exit
condition. Address any comments it does post like any other AI bot.wait-for-activity.sh with the 30-min timeout and
move on if nothing changes.cubic-dev-ai[bot], coderabbitai[bot], copilot[bot] and
variants): handle
autonomously. Verify the finding, fix or push back with reasoning, reply
in-thread, resolve thread.sonarqubecloud[bot], github-actions[bot],
netdata-bot[bot]): read for signal (e.g. quality
gate status). They don't usually require a reply.gh CLI authenticated for the repo. Nothing else.
The skill reads upstream (or origin) from git remotes to derive the
repo slug. Override with PR_REPO_SLUG=owner/repo if working cross-repo.
State for each PR is cached under <repo-root>/.local/audits/pr-reviews/pr-<N>/:
pr.json -- top-level PR metadataissue-comments.json -- top-level PR comments (REST)review-comments.json -- inline review comments (REST)reviews.json -- review submissions with body (REST)review-threads.json -- per-thread, with isResolved (GraphQL)summary.txt -- human-readable triage summaryThe order is: gather all findings -> address them per-thread / per-finding -> check CI for failures the PR caused -> push -> retrigger -> wait -> loop.
bash .agents/skills/pr-reviews/scripts/fetch-all.sh <PR_NUMBER>
Tail-prints a summary.txt that shows the per-author count and the list of
open review threads. Use this as the input to the rest of the cycle.
bash .agents/skills/pr-reviews/scripts/fetch-sonar-findings.sh <PR_NUMBER>
SonarCloud findings are NOT delivered as inline GitHub comments -- only a QualityGate summary is. The actual issue list lives behind the SonarCloud API. This script writes:
.local/audits/pr-reviews/pr-<N>/sonar-issues.json.local/audits/pr-reviews/pr-<N>/sonar-hotspots.jsonRequires the same .env config the sonarqube-audit skill uses
(SONAR_TOKEN, SONAR_HOST_URL, SONAR_PROJECT). If .env is missing,
the script prints what's needed and exits.
Run bash .agents/skills/pr-reviews/scripts/ci-status.sh <PR> once early to capture which checks are failing
right now. You're looking for failures caused by the current PR
(typo in a YAML file you added, a script that doesn't pass shellcheck,
a build that breaks because of the diff). DO NOT fix CI yet -- just note
the failures as input alongside review comments and Sonar findings. They
all get addressed in the same iteration so a single push covers them.
bash .agents/skills/pr-reviews/scripts/list-open-threads.sh <PR_NUMBER> # full bodies
bash .agents/skills/pr-reviews/scripts/list-open-threads.sh <PR_NUMBER> --short # one line per thread
The "short" output is a table: thread-id | path:line | author. The full
form prints every comment in each thread.
This is per-thread, not batched. Do not prepare a list of replies and fire them in a loop. Do not post all replies first and resolve all later. Walk one thread at a time:
For thread N:
bash .agents/skills/pr-reviews/scripts/reply-thread.sh <PR> <comment-id> "<reply>"
<comment-id> is the databaseId of the FIRST comment in the thread
(from review-threads.json -> .[].comments.nodes[0].databaseId).posted reply id=.... Resolving a thread whose reply failed
hides it from the needs-attention view with nothing written in it, which
reads to a human as a silently dismissed review.
bash .agents/skills/pr-reviews/scripts/resolve-thread.sh <thread-id>
<thread-id> is the GraphQL node id (review-threads.json -> .[].id,
starts with PRRT_). Resolving immediately after replying takes the
thread out of the "needs attention" view; leaving threads open without
resolution accumulates noise.Then move to thread N+1. Reply-and-resolve, reply-and-resolve. Never queue them up.
The reason: the order makes intent visible to humans watching the PR -- they see "agent posted reply, agent resolved" as one motion per thread, not "agent dumped 14 replies, then dumped 14 resolves". Bulk operations look mechanical and erode trust in the address pass.
For each issue in sonar-issues.json and each hotspot in
sonar-hotspots.json:
random() callsites.sonarqube-audit skill provides
sonar-mark.sh fp <KEY> "<reason>" to mark it False Positive
directly in SonarCloud. Comments are ASCII-only (Cloudflare).sonar-issues.json has zero issues that we caused.For Sonar there is no "thread reply" -- you address the issue with
either a code fix or a sonar-mark.sh action. There's nothing to
resolve in GitHub for Sonar findings.
Reviewers run in parallel. Multiple bots and humans can be appending findings WHILE you're addressing the current batch. If you push the moment your queue is empty, the findings that arrived during this iteration get attributed to your fresh commit instead of the previous one -- and on the next round you end up "fixing" findings that no longer apply because you addressed them implicitly with the next push. The result: chronic desync, where your commit and the reviewers' findings are always one round apart.
The fix: a sync barrier immediately before push. Re-fetch ALL sources (comments, Sonar, CI) one more time. If ANY new finding has arrived since you last looked, loop back to step 2 -- address those new findings in the SAME upcoming push -- then re-fetch again. Only push when a fresh fetch comes back with no new findings against the current HEAD. This guarantees you and the reviewers are synchronized.
bash .agents/skills/pr-reviews/scripts/fetch-all.sh <PR_NUMBER>
bash .agents/skills/pr-reviews/scripts/fetch-sonar-findings.sh <PR_NUMBER>
bash .agents/skills/pr-reviews/scripts/ci-status.sh <PR_NUMBER>
The ci-status.sh line is the third source: a CI failure that is
CAUSED by this PR's changes (added a script that doesn't pass
shellcheck, broke a YAML parse, etc.) is in scope and must be folded
in. CI failures unrelated to this PR are noted, surfaced to the user
at the end, but not fixed here.
If summary.txt shows any new open thread or sonar-issues.json shows
any new issue you haven't addressed yet, do NOT push. Loop back to
step 2 and address them first. Then re-run the fetch. Only push when
the fetch is clean.
The same loop applies during the iteration: if you re-fetched while addressing the previous batch and saw new findings drop in, fold them into the same push rather than dispatching a half-done batch.
This is the most important pre-push step. Skipping it is what makes review cycles last for hours.
After you have made all the fixes for the current iteration's findings
but BEFORE running git push, spawn a subagent to re-review the WHOLE
PR diff (not the small change you just made). The orchestrator's
context is already loaded with the recent fixes; the subagent's clean
context is what gives an honest second look.
Why this is non-negotiable:
How to invoke:
Use the orchestrator's Agent / subagent tool (whatever the harness provides). Pass the subagent the PR diff (or the list of touched files) and ask it to:
Then the orchestrator addresses every finding the subagent returns BEFORE push. Loop the subagent if necessary until it returns a clean review. Only then proceed to step 4b.
A good subagent prompt template:
Re-review PR <N> end-to-end. The current diff is on branch <X>; the base is <master|...>. Recent fixes addressed: <list>. For the WHOLE diff (not just the recent fixes), find:
- Similar patterns to the ones recently fixed that were NOT pointed at by reviewers but where the same reasoning applies.
- Issues the recent fixes may have introduced (regressions, broken behavior, dropped features).
- Anything in the diff that does not match the project's conventions (AGENTS.md, sibling files, the rest of the repo). Report a flat list of file:line + class + suggested fix. Be exhaustive; do not stop at 3-7 findings.
bash .agents/skills/pr-reviews/scripts/ci-status.sh <PR_NUMBER>
Exit codes:
0 -- all green, safe to push2 -- runs in progress -- IGNORE this; push anyway. Waiting for CI
between iterations destroys throughput. The new push triggers fresh CI
on the new code, which is what matters.3 -- runs failing -- fix the failures and bundle them into the push.CI failures unrelated to this PR (a flaky test on a different module, an infra outage) are NOT in scope for this PR -- note them, surface to the user, move on. Do not make drive-by fixes here.
After pushing the fix commit(s):
bash .agents/skills/pr-reviews/scripts/trigger-cubic.sh <PR_NUMBER>
bash .agents/skills/pr-reviews/scripts/trigger-coderabbit.sh <PR_NUMBER>
cubic and coderabbit re-review when mentioned in a new top-level PR comment.
Copilot is deliberately absent: see rule 8. If you add a reviewer to
PR_AI_BOT_RE, add its re-trigger here in the same change -- a classified
reviewer with no re-trigger is silently skipped every iteration.
bash .agents/skills/pr-reviews/scripts/wait-for-activity.sh <PR_NUMBER>
Default timeout 30 min, poll every 30 s. Returns 0 on new activity, 124 on timeout. Both bots typically post a "no new findings" comment when they have nothing left, so the loop ends naturally on a clean PR.
What counts as "new activity":
Go back to step 1. Continue until ALL of these are true:
fetch-all.sh reports all review threads resolved.fetch-sonar-findings.sh reports zero open issues / hotspots that
this PR introduced (or the remaining ones are explicitly marked FP /
WontFix).ci-status.sh reports no failures caused by this PR (failures
unrelated to the PR are noted, surfaced to the user, but not fixed).When wait-for-activity.sh times out (30 min):
ci-status.sh. If checks are still running, surface to the
user and stop -- the PR is in a clean intermediate state.When the loop ends, summarize for the user:
Commit messages on the address-the-comments cycle should describe the change, not the reviewer or the cycle:
Never reference an AI tool by name in commit messages or PR bodies. The work matters; the tool that flagged it does not.
(Comments on the PR are an exception when they're operational mentions
required by the bot itself: @cubic-dev-ai please review again is a
direct trigger for that bot, and the trigger script enforces it. Outside
operational triggers, the same rule applies to comments.)
Be substantive but brief. The bot's prompt-text is verbose; your reply doesn't have to be. Examples:
Maintainer / dev / community comments go to the user FIRST. Your message should:
Then act per their direction. Do not respond to humans on the user's behalf without explicit direction.
| Bot | Role | Re-trigger |
|---|---|---|
cubic-dev-ai[bot] | Line-level code review | New PR comment mentioning @cubic-dev-ai |
coderabbitai[bot] | Line-level code review | trigger-coderabbit.sh (@coderabbitai review) -- NEVER @coderabbit, that is a different, unrelated GitHub user |
copilot[bot] | Line-level code review | Not re-triggered -- no credits (see rule 8) |
sonarqubecloud[bot] | Quality-gate status | Auto, on each scan run -- read its issue comment |
github-actions[bot] | CI status / labels | Auto, on each workflow run |
netdata-bot[bot] | Repo automation (labels, etc.) | Auto |
If a new AI reviewer appears in the project, classify it by adding to
PR_AI_BOT_RE in _lib.sh so the skill recognizes it.
The mention is @coderabbitai, never @coderabbit. @coderabbit is a
real, unrelated GitHub user; mentioning it pings a stranger on every
iteration and never reaches the bot. trigger-coderabbit.sh hardcodes the
correct handle -- do not hand-write the mention. The same care applies to
@cubic-dev-ai. Before posting any comment containing an @, check the
handle against the bot directory above.
coderabbitai[bot] posts line-level findings, not just summaries. It was
originally classified here as informational; it is an AI reviewer and its
threads need the same verify-reply-resolve treatment as cubic's.
coderabbit cites external URLs (learn.netdata.cloud, upstream GitHub) as
evidence. Those citations are often directionally right but not
authoritative for the branch under review -- verify against the source in the
checkout before acting. Example: it correctly flagged that a Function needs a
signed-in identity, but the proof is the HTTP_ACCESS_* flags in the
producer, not the doc page it linked.
Both reviewers will flag a generated page's content when the real defect is in the generator or the shared template. Fix the producer, regenerate, and say so in the reply -- otherwise the same finding returns on the next vendor page.
| Symptom | Likely cause |
|---|---|
fetch-all.sh returns suspiciously round counts | Pagination missed pages. Re-run; fetch-all auto-probes when count is a multiple of 100. |
A GraphQL helper script fails with cursor_args[@]: unbound variable | macOS Bash 3.2 plus set -u treats empty array expansion as unbound. Keep gh api argument arrays non-empty before expansion or branch the first-page GraphQL call. This affected both fetch-all.sh and wait-for-activity.sh. |
reply-thread.sh -> 404 | Wrong comment id (use databaseId from review-threads.json, not the GraphQL node id). |
reply-thread.sh -> line N: 2: usage, yet the thread ends up resolved | The comment id expanded to empty AND the resolve ran anyway. Never chain reply and resolve so that resolve can run after reply fails: run reply-thread.sh, confirm it printed posted reply id=..., THEN resolve. See the bash-vs-zsh note below. |
An associative-array lookup (${MAP[key]}) is empty in a helper loop | The interactive shell here is zsh, not bash. declare -A plus ${MAP[key]} does not behave the same, so ids silently expand to nothing. Pass literal ids, or drive the loop from python3 output one line at a time, rather than building a shell map. |
resolve-thread.sh -> "thread not found" | Used REST id instead of GraphQL node id. |
trigger-copilot.sh succeeds but no new review | Expected. The org has no Copilot review credits, which is why it is not in the loop. Do not wait on it. |
trigger-cubic.sh succeeds but no new review | cubic ignores comments without an explicit @cubic-dev-ai mention. The script always prepends it. |
ci-status.sh exits 2 (running) | CI hasn't finished. Push anyway -- waiting on CI between iterations destroys throughput. The next push triggers a fresh CI run on the new code, which is what matters. (See Step 4b.) |
| Bot keeps re-flagging the same line after a fix push | The bot didn't see the new commit because it wasn't re-triggered. |
wait-for-activity.sh 124 timeout | Bots are silent -- could be done, could be quota-limited. Check summary.txt; if all threads resolved, you're done. |
If you (the agent) discover a new pattern, gotcha, working flow, correction,
or any piece of knowledge while running this skill -- update this SKILL.md
AND commit it BEFORE proceeding. Knowledge that isn't committed is lost.
Examples of things to capture:
PR_AI_BOT_RE)