---
name: pr-monitoring
description: Use after a pull request has been opened and the task is not finished until it lands. Triggers on "PR is open", "CI is running", "is it merged yet", a PR that is green but not merged, fresh CI that might still flip, or being tempted to report a task done at PR-open. GitHub / gh CLI based.
---

# PR Monitoring

## Overview

**Open is not landed. Green is not merged.** A PR you opened is unfinished work until it has actually merged with nothing outstanding — or you have named a blocker only a human can clear. You are the machine that keeps watching so a human doesn't have to babysit the PR; declaring "done" at PR-open (or at a freshly-green CI) is exactly the human-backpressure you exist to replace.

Two traps the base instinct misses even when it resists "open = done":
1. **A green check can flip red.** Late-starting jobs and slow stages mean an initial all-green is not a final all-green.
2. **The watch ends at merge, not at first green.** New commits, reviewer comments, and conflicts arrive *after* CI goes green.

## When to Use

- You (or the main agent) just opened a PR, or CI is running on one, and the goal is to get it merged.
- Asked "is it merged / did it land / is CI done yet?" on a PR you own.
- Tempted to report the task complete because the PR is open or CI just went green.

**Not for:** opening the PR itself; deciding *whether* to open one; code review of the diff (that is the reviewer skills). This is purely the watch-until-landed loop.

## "Landed clean" — the sufficiency bar

Report done only when **all** hold; otherwise keep monitoring or name the blocker:

- PR **state is MERGED** (not just approved, not just green).
- Every **required** check is green — confirmed, not assumed (`statusCheckRollup`).
- No **unresolved** review-bot or human comments.
- No **conflict** (`mergeStateStatus` is not `DIRTY`) and the branch is not **stale** (`BEHIND`) — ideally `CLEAN`.

## The monitoring loop

```dot
digraph pr_monitor {
    "PR open" [shape=box];
    "Watch checks to completion" [shape=box];
    "All required checks green?" [shape=diamond];
    "Read failing logs, fix root cause, push" [shape=box];
    "Hold a green minute (≥60s continuously green)" [shape=box];
    "Anything flipped red / new check started?" [shape=diamond];
    "Comments or conflicts outstanding?" [shape=diamond];
    "Address them (push fix / resolve / reply)" [shape=box];
    "Merge if authorized" [shape=box];
    "Merged?" [shape=diamond];
    "STOP: name the human blocker" [shape=octagon];
    "Landed clean — report done" [shape=doublecircle];

    "PR open" -> "Watch checks to completion";
    "Watch checks to completion" -> "All required checks green?";
    "All required checks green?" -> "Read failing logs, fix root cause, push" [label="no"];
    "Read failing logs, fix root cause, push" -> "Watch checks to completion";
    "All required checks green?" -> "Hold a green minute (≥60s continuously green)" [label="yes"];
    "Hold a green minute (≥60s continuously green)" -> "Anything flipped red / new check started?";
    "Anything flipped red / new check started?" -> "Watch checks to completion" [label="yes — restart clock"];
    "Anything flipped red / new check started?" -> "Comments or conflicts outstanding?" [label="no"];
    "Comments or conflicts outstanding?" -> "Address them (push fix / resolve / reply)" [label="yes"];
    "Address them (push fix / resolve / reply)" -> "Watch checks to completion";
    "Comments or conflicts outstanding?" -> "Merge if authorized" [label="no"];
    "Merge if authorized" -> "Merged?";
    "Merged?" -> "STOP: name the human blocker" [label="no — needs approval/permission"];
    "Merged?" -> "Landed clean — report done" [label="yes"];
}
```

## gh mechanics (quick reference)

| Need | Command |
|------|---------|
| Watch checks (background, 1-min poll, 10-min cap) | `timeout 600 gh pr checks <pr> --watch --interval 60 &` |
| Structured status in one shot | `gh pr view <pr> --json state,mergeStateStatus,reviewDecision,statusCheckRollup` |
| Read only the failing job's logs | `gh run view <run-id> --log-failed` |
| Read review-bot / human comments | `gh pr view <pr> --comments` |
| Merge when authorized | `gh pr merge <pr> --squash` (or repo's strategy) |

Key `mergeStateStatus` values: `CLEAN` (mergeable), `BLOCKED` (needs approval/required check), `BEHIND` (must update branch), `DIRTY` (conflict), `UNSTABLE` (a non-required check failing).

## Cadence & backgrounding (defaults)

**Always monitor in the background — never block the session.** The user must be able to keep prompting while the PR is watched, so run the watch as a background job (`&`, or your harness's background-task / re-invoking monitor) and check back on it. A foreground `gh pr checks --watch` that holds the terminal is wrong here — it freezes the user out.

- **Default window: 10 minutes** of active polling per round (`timeout 600`).
- **Default poll interval: 1 minute** (`--interval 60`) — not a tight loop, not once-and-walk-away.
- **When the 10-min window elapses without landing:** report the current state (which checks are pending/red, review status, mergeability) and kick off another background round. Don't declare done and don't silently drop the watch — the task is still "land it"; the window only bounds each pass so you surface progress and stay responsive.
- Defaults only: a `BACKPRESSURE.md` may override the window/interval, and an obviously fast/slow pipeline can adjust — but the **background, non-blocking** rule always holds.

Background ≠ abandoned — the loop is not over until merged.

## Handling each outcome

- **A check fails:** read `--log-failed`, fix the **root cause**, push. Do **not** re-run jobs hoping for a pass — a red check is unresolved work, and a flaky-looking failure on your own change is usually your change. (See [[general-code-review]] if the failure points at a logic/type defect.)
- **Late comments arrive:** review bots (CodeRabbit, linters) and humans often comment 1–3 min *after* green. Address real findings; reply to or dismiss noise — don't merge over unresolved blocking comments.
- **`BEHIND`:** update the branch from base, which re-triggers CI — back to watching.
- **Can't merge** (`BLOCKED`, missing approval, no permission): that is a genuine human blocker. **STOP and name it precisely** ("CI green and mergeable; branch protection needs one human approval") — do not report success.

## Common rationalizations

| Rationalization | Reality |
|-----------------|---------|
| "PR is open, task done — I'll log off" | Open ≠ landed. The watch has barely started. |
| "Local tests passed, CI will be the same" | CI is a different environment; that's the point of CI. Confirm green on CI. |
| "All 7 checks are green, we're done" | Green ≠ merged, and a 30-second-old green can still flip. Hold the green minute, then merge. |
| "A check went red, just re-run it" | Re-run-until-green hides a real failure. Read the log, fix the cause. |
| "Green now, I can stop watching" | Comments and conflicts land after green. Watch until merged. |
| "I'll report done and mention CI is still running" | A status that depends on an unverified future is a false completion claim. |
| "I'll just `--watch` in the foreground until it's done" | That blocks the session so the user can't prompt. Run it in the background, poll every minute (default 10-min window). |

## Red flags — STOP

- About to report the task complete while the PR is **open but not merged**.
- About to trust an all-green that has been green for **less than a minute**.
- About to **re-run** a failing check instead of reading its log.
- About to **stop watching** at first green instead of at merge.
- Treating "can't merge, needs approval" as done instead of as a **named blocker**.
- Blocking the session with a **foreground** watch instead of monitoring in the **background**.
