Watch an open pull request for new CI failures and review comments and drive it to a clean state. Uses the Monitor tool to stream PR status changes in real time so reactions are immediate instead of polled. Handles CodeRabbit nitpicks with judgment (do not blanket-skip) and treats "out of date" comments as still-relevant until proven otherwise. Replies to and resolves each thread as it's addressed. Triggers: "monitor the PR", "watch the PR for comments", "keep an eye on the PR", "address PR f...
Scanned 5/28/2026
Install via CLI
openskills install sageox/ox---
name: monitor-pr
description: >
Watch an open pull request for new CI failures and review comments and
drive it to a clean state. Uses the Monitor tool to stream PR status
changes in real time so reactions are immediate instead of polled.
Handles CodeRabbit nitpicks with judgment (do not blanket-skip) and
treats "out of date" comments as still-relevant until proven otherwise.
Replies to and resolves each thread as it's addressed. Triggers:
"monitor the PR", "watch the PR for comments", "keep an eye on the PR",
"address PR feedback as it lands", /monitor-pr.
---
# monitor-pr
Drives a pull request to green by **streaming** its state through the
`Monitor` tool and reacting the moment a check fails or a new review
comment lands. Designed for CodeRabbit-reviewed PRs but works for human
reviewers too.
## Architecture: use the `Monitor` tool, not a sleep loop
**This is load-bearing — do not substitute a Bash `sleep` loop.** A loop
in a single `Bash` call blocks the agent until it exits, so the agent
can't react to events concurrently and misses interleaved work.
`Monitor` streams each stdout line as a notification into the
conversation, so the agent keeps full agency between events.
Start exactly one monitor at the top of the task with:
- `persistent: true` — PR reviews can take hours; don't let a timeout
kill the watch mid-review.
- `description` — specific, e.g. `"PR #493 state changes"`, because it
appears in every notification.
- The polling script below as `command`.
Stop the monitor with `TaskStop` only when the exit condition is met or
the user cancels.
## The polling script
Resolve PR metadata first (once, before starting the monitor):
```bash
gh pr view --json number,url,headRepository,headRepositoryOwner,baseRepository
```
Then start the `Monitor` with this command (substitute `PR`, `OWNER`,
`REPO`). It emits **one line only when state changes** — a quiet PR
produces zero events, an eventful PR produces one event per transition.
```bash
PR=<number>; OWNER=<owner>; REPO=<repo>
last=""
while true; do
checks=$(gh pr checks "$PR" --json bucket 2>/dev/null || echo '[]')
failing=$(jq '[.[] | select(.bucket=="fail" or .bucket=="cancel")] | length' <<<"$checks")
pending=$(jq '[.[] | select(.bucket=="pending")] | length' <<<"$checks")
unresolved=$(gh api graphql --paginate -f query='
query($o:String!,$r:String!,$n:Int!,$endCursor:String){
repository(owner:$o,name:$r){
pullRequest(number:$n){
reviewThreads(first:100, after:$endCursor){
nodes{ isResolved }
pageInfo{ hasNextPage endCursor }
}
}
}
}' -f o="$OWNER" -f r="$REPO" -F n="$PR" 2>/dev/null \
| jq -s '[.[].data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved==false)] | length')
state="fail=${failing:-?} pending=${pending:-?} unresolved=${unresolved:-?}"
if [ "$state" != "$last" ]; then
if [ "$failing" = "0" ] && [ "$pending" = "0" ] && [ "$unresolved" = "0" ]; then
echo "clean: all checks pass, none pending, zero unresolved threads"
else
echo "change: $state — triage needed"
fi
last="$state"
fi
sleep 60 || exit 0
done
```
Monitor discipline:
- **60s poll floor.** Don't drop below; you'll rate-limit yourself against
the GitHub API and waste tokens on noise.
- **Only state transitions emit.** Stable state is silent.
- **Transient failures are tolerated** (`2>/dev/null` on both `gh`
calls, `|| echo '[]'` on `gh pr checks`) so a single API blip yields
a degraded poll instead of killing the watch. The `${var:-?}`
fallbacks in the state string ensure partial results still produce
an event rather than a blank state.
- **One line per event** keeps notifications terse — Monitor turns every
stdout line into a chat notification.
## Reacting to a `change:` event
When the monitor emits `change: fail=N pending=P unresolved=M`, do the
following. Keep the monitor running the whole time.
### 1. Failing checks
```bash
gh pr checks <pr>
gh run view --log-failed <run-id>
```
Fix the root cause in code. Never bypass with `--no-verify`, flaky-retry
loops, or skip directives.
### 2. Fetch every review thread with resolution + outdated state
REST doesn't expose `isResolved`/`isOutdated`. Use GraphQL, and
**paginate** via `--paginate` + an `$endCursor` variable so PRs with >100
threads don't silently truncate:
```bash
gh api graphql --paginate -f query='
query($owner:String!, $repo:String!, $number:Int!, $endCursor:String) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$number) {
reviewThreads(first:100, after:$endCursor) {
nodes {
id
isResolved
isOutdated
path
line
originalLine
comments(first:50) {
nodes { databaseId author { login } body createdAt }
}
}
pageInfo { hasNextPage endCursor }
}
}
}
}' -f owner=<owner> -f repo=<repo> -F number=<pr>
```
`gh --paginate` walks `pageInfo.endCursor` until `hasNextPage: false` and
emits one JSON document per page to stdout. When consuming, slurp with
`jq -s` and iterate across pages (e.g. `jq -s '[.[].data.repository.pullRequest.reviewThreads.nodes[]] | ...'`).
### 3. Triage each unresolved thread
Judge each one individually. Do **not** blanket-skip any category.
- **Human comment** — address it.
- **CodeRabbit actionable** — address it.
- **CodeRabbit _nitpick_** — DO NOT auto-dismiss. CodeRabbit is often
overly polite and hides real issues under "nitpick". Read each one and
decide: is this a legit correctness/clarity/safety concern, or purely
stylistic noise that conflicts with repo conventions? Only skip if the
suggestion is actively wrong, contradicts `CLAUDE.md`, or is irrelevant
to the change's intent. When in doubt, apply the fix.
- **Thread marked `isOutdated: true`** — DO NOT skip. "Outdated" means
the line numbers moved since the comment was written, **not** that the
feedback is obsolete. Re-read the comment against the current code at
that region and decide whether the concern still applies. Usually it
does.
### 4. Fix in code
Actually edit source. Never reply-without-fix. If a comment implies a
design decision the user should own, stop your own work and ask — but
**leave the monitor running**. It's silent while state is stable, costs
nothing, and will resume emitting as soon as the discussion ends in a
push or a resolve.
### 5. Reply + resolve each addressed thread
```bash
# Reply (the in_reply_to form of the review comments API)
gh api repos/<owner>/<repo>/pulls/<pr>/comments/<comment-databaseId>/replies \
-f body="Fixed."
# Resolve
gh api graphql -f query='
mutation($threadId:ID!) {
resolveReviewThread(input:{threadId:$threadId}) {
thread { isResolved }
}
}' -f threadId=<thread-node-id>
```
For threads you intentionally skipped (e.g., a nitpick that's wrong),
reply with one sentence explaining why, then still resolve the thread.
### 6. Commit + push
Bundle the round into one commit following repo style (`CLAUDE.md`:
one-line `type(scope): summary`, ≤72 chars).
This repo's `CLAUDE.md` says: **"Always confirm with human before doing
a git commit or a git push."** Honor that unless the user has explicitly
told you to run autonomously.
**Do not stop the monitor after pushing.** A new push triggers new CI
runs and may draw new CodeRabbit follow-ups; the monitor will fire again
when they land, and the loop continues naturally.
## Exit
When the monitor emits `clean: ...`:
1. `TaskStop` the monitor.
2. Report a summary: what was fixed, what was intentionally skipped and
why (one bullet per skipped thread), final check + thread counts.
## Guardrails
- **Always use `Monitor`.** Not `sleep` in a Bash call, not manual
repeated polling. Monitor streams events so the agent reacts
immediately and runs concurrently with other work.
- **Never skip outdated comments blindly.** `isOutdated` = line moved.
- **Never blanket-dismiss CodeRabbit nitpicks.** Judge each on merit.
- **Never bypass failing checks** with `--no-verify` or similar.
- **Confirm before `git push`** unless running autonomously.
- **Pause and ask** if a comment implies a design decision the user
should own, rather than guessing — but **keep the monitor running**
during the discussion. It's silent while state is stable.
- **One-line commit messages**, detail in the PR body.
## Related
- `CLAUDE.md` — repo commit/PR conventions, CodeRabbit reply protocol.
- `Monitor` tool — session-length, `persistent: true`, one stdout line =
one event, stop with `TaskStop`.
No comments yet. Be the first to comment!