---
name: watch-pr
description: Watch the current branch's PR — reply to and resolve review threads, fix failing checks, and optionally merge when ready
---

# Watch PR

Watch the open pull request for the current branch: handle review conversations,
fix failing status checks, and — when asked — merge once everything is green.
Repo is `adamayoung/TMDb`. GitHub reads/writes (PR state, checks, branch update,
merge) use the **GitHub MCP** (`mcp__github__*`, owner/repo from the `origin` remote);
`gh` is kept for the **blocking CI wait** (`gh pr checks --watch`, which the MCP has
no equivalent for). If an MCP write fails with **401/403** (PAT expired or missing
scope), fall back to the equivalent `gh` command.

**Mode** — check the arguments passed to this skill (shown at the end). If they
include `merge` (e.g. `/watch-pr merge` or "merge when ready"), enable
**merge-when-ready**; otherwise run in **watch-only** mode.

**Run in the background.** Watching blocks on CI for minutes at a time, so run the
watch as a background task — the user can keep interacting while it runs, and is
pinged when the PR needs attention or is ready. Don't tie up the foreground on a
wait loop.

## 0. Find the PR

**A PR number in the arguments wins.** If the arguments include a PR number
(e.g. `/watch-pr 123` or `/watch-pr merge 123`), watch that PR and skip branch
discovery entirely. A caller that launches this skill **in the background**
(e.g. `/deliver` Phase 10, which may move the session to another deliverable's
worktree while the watch runs) must pass the number — the watch must never
depend on "current branch" staying stable for its lifetime.

Otherwise, find the open PR for the current branch with `mcp__github__list_pull_requests`
(owner/repo from the `origin` remote, `head: <owner>:<branch>`, `state: open`), then
read its details with `mcp__github__pull_request_read` method `get` — `number`,
`html_url`, `state`,
`head.ref`, and **`mergeable_state`** (the REST merge state: lowercase
`clean`/`blocked`/`behind`/`unstable`/`dirty`/`unknown`/`draft` — **not** `gh`'s
uppercase `mergeStateStatus`). Read the check runs separately with method
`get_check_runs` (§3).

- No PR for the current branch → stop and tell the user (suggest `/pr`).
- State not `open` → stop and report.
- **Thread data is a separate call** — `pull_request_read` method `get` does not
  return review threads. Fetch them with method `get_review_comments` (delegated to
  `/review-pr-threads`); never expect thread data on the PR `get`.
- **Pin the tree to the PR, and re-check it every pass.** The number alone is
  not enough: both sweeps in §1 **edit and push the current working tree**, so
  a drifted CWD doesn't just read the wrong PR, it commits to the wrong branch.
  Before each pass assert `git branch --show-current` equals the PR's
  `head.ref` (already fetched above, so this is free). Mismatch → **stop and
  report; never sweep.** This is the case §0 exists for — a background watch
  outlives the session's CWD, and `/deliver` Phase 10 moves to the next
  deliverable's worktree while this loop is still running.

Keep a **run ledger** in your working notes: resolved thread IDs, a topic
signature per handled thread (`path:line` + short gist), and a fix-attempt
counter per failing check. Use it to avoid repeating work (see Loop Guard).

## 1. Watch loop

Repeat the pass below until the PR is **ready** (§3) or **stuck** (§2).

### 1a. Review threads

Delegate the thread sweep to **`/review-pr-threads <number>`** — **always pass
the PR number.** Without it the skill falls back to "the current branch's PR"
(`review-pr-threads` §0), re-introducing exactly the dependency §0 forbids. It resolves the
currently-unresolved threads in one pass (assess → fix+verify / reply-only →
reply → resolve), reusing **this run's ledger** so a topic you already fixed isn't
re-edited, and returns a summary (fixed w/ SHAs, replied-only, left-for-user,
counts, whether it pushed).

It is a **single sweep** by design — the across-push convergence is *this* loop's
job: fold its summary into the ledger, and if it pushed fixes, the next pass picks
up any fresh threads the `claude-review` bot raises (gated to Critical/High). Do
not duplicate its per-thread logic here.

### 1b. Status checks

Delegate failing-check fixing to **`/fix-pr-checks <number>`** — **always pass
the PR number**, for the same reason as §1a. It routes each failing
check to the right diagnosis skill (`/diagnose-ci-failure` or
`/diagnose-integration-failure`) via a Haiku subagent, applies and verifies the
fix, commits, pushes once, and returns a summary (fixed w/ SHAs, exhausted,
skipped, pending, whether it pushed). It shares **this run's ledger**, so the
3-attempt cap per check is honoured across passes. Fold its summary into the
ledger; if it pushed, the next pass re-checks.

**Waiting stays here** (orchestration, not the fix primitive): if checks are
`pending` and nothing is failing, block efficiently with `gh pr checks --watch`
rather than polling, then loop. If `/fix-pr-checks` reports a check **exhausted**,
stop and report it per the Loop Guard.

### 1c. Failing checks not caused by this PR (usually a flaky integration test)

`/fix-pr-checks` assumes the fix belongs on **this** branch. That is wrong when
the failing check — most often a **live integration test** — is broken or flaky
**independently of this PR's diff**. Patching it onto the feature branch would
muddy the PR's scope and leave the test broken for everyone else. Triage before
fixing here:

1. **Is the failing test in this PR's diff?** `mcp__github__pull_request_read`
   method `get_files` (`pullNumber: <n>`) — if the failing test's file is **not** in
   that list, this PR did not cause it.

2. **Transient or deterministic?** Re-run the failed jobs once with
   `mcp__github__actions_run_trigger` method `rerun_failed_jobs` (`run_id: <id>`),
   then watch the re-run (the loop's `gh pr checks --watch`, §1b). Passes on re-run →
   a transient live-API flake; note it and continue. Fails again **and** not in this
   PR's diff → a **pre-existing** problem (e.g. a brittle live-data assertion).

3. **A pre-existing failure is a `main` problem — hand it to
   `/fix-integration-failures`**, which fixes it on its own branch off `main`,
   runs `make ci`, and opens/merges a PR (never on this feature branch). Tell the
   user you're opening a **second** PR to unblock this one; if they'd rather you
   stop, report and stop. Once that fix is on `main`, bring this PR's branch up to
   date (`mcp__github__update_pull_request_branch`, `pullNumber: <n>`) and re-watch —
   the check now passes.

## 2. Loop guard (do not get stuck)

- **Thread dedup is `/review-pr-threads`' job** — it shares this ledger, so it
  won't reprocess a handled thread or re-edit a topic already fixed this run (it
  replies with the earlier SHA and resolves). Keep passing it the same ledger.
- **Check-fix attempts are `/fix-pr-checks`' job** — it shares this ledger and
  caps each check at **3** fix→push attempts. When it reports a check
  **exhausted**, **stop** and report it to the user — never loop forever.
- The `claude-review` bot re-reviews on every push (`synchronize`), so your fixes
  trigger fresh reviews — this is expected, not new work to fear. By policy it now
  posts inline threads **only for Critical/High** findings (Medium/Low live in its
  summary comment, which is advisory — do not turn summary bullets into code
  changes). A converging PR should see each push's inline batch shrink; if the same
  severity-gated topic reappears across pushes, treat it as noise per the rule
  above (reply with the earlier SHA, resolve, don't re-edit).
- **Re-sweep after every push — "ready" is only true of the current tip.** Any push
  to the branch (a check fix, *and* a caller's exceptional post-gate commit such as
  an approved skill edit or a `/deliver` retro amendment) re-triggers
  `claude-review`, which can post a fresh
  Critical/High thread that **blocks the merge** (`required_review_thread_resolution`).
  Never declare ready off a thread/check snapshot taken *before* the latest push:
  after the last push settles, run one more full pass (thread sweep + check
  re-confirm) before §3. A single early "0 unresolved" check is not a standing
  guarantee.
- End the loop when a full pass resolves no new threads and has no actionable
  check failures. Hard backstop: ~10 passes, then report and stop.
- Waiting: use `gh pr checks --watch` for in-flight CI. When only waiting on a
  human to review, pause and resume later with `ScheduleWakeup` (a few minutes
  while CI is active; longer when idle). This skill also composes with `/loop`
  if the user prefers harness-driven cadence.

## 3. Ready / merge

The PR is **ready** when every review thread is resolved, no check is failing or
pending, AND the branch is **up to date with `main`** (the `main` ruleset requires
it before merge).

**Verify check completeness explicitly — a running check is not a pass.** Read the
checks with `mcp__github__pull_request_read` method `get_check_runs` (owner/repo from
`origin`, `pullNumber: <n>`). "No check failing" is **not** the same as "all checks
passed": a run still `in_progress` / `queued` has **no `conclusion` yet**, so a filter
like "any conclusion != success?" reports it as *absent*, not *pending* — reading as
green when it isn't. Confirm readiness **positively**: every check run has
`status == "completed"` **and** `conclusion == "success"` on the current head commit.
`get_check_runs` is scoped to the head commit, but a re-run can still leave duplicate
rows for a check name — key on the latest run per name. Do **not** infer green from a
`gh pr checks --watch` exit alone; re-read the check runs. And when `mergeable_state`
is `blocked`, **rule out a pending required check first** (it is the common cause)
before attributing the block to a review/policy rule like code-owner review.
(Bit #361: a still-running "Build and Test" was misread as green and the block was
wrongly pinned on code-owner review.)

**Rebase before declaring ready, never after.** `main` advances while you watch
(other PRs merge), leaving the branch `BEHIND`. The moment the PR is otherwise
green, bring it up to date — `mcp__github__update_pull_request_branch`
(`pullNumber: <n>`; a merge of `main`, no force-push) — and wait for the re-triggered
CI to go green again *before* you call
it ready. "Ready" must mean "mergeable right now": never surface a `BEHIND` PR as
ready and leave the user waiting on a rebase + re-run. Update **once** at the ready
point, not eagerly on every `main` advance — each update re-runs the full ~4–7 min
CI matrix.

**On ready, move the issue to `In review` — before reporting.** Ready means the
PR is waiting on a human, which is exactly what that column tells them, and the
board is how they see it without reading this run's output. Take the issue number
from the **closing keyword in the PR body** (`Closes #NNN` / `Fixes #NNN`), which
`/pr` requires on every PR that has an issue. That is deliberately the only
source: it is the same link GitHub uses to close the issue on merge, so it cannot
disagree with what actually happens, and it needs no extra argument — a second
bare number beside the PR number would be ambiguous to parse.
Column vocabulary and the exact call:
[`.github/ISSUE_FILING.md`](../../../.github/ISSUE_FILING.md) → *Board status —
the column lifecycle*. No issue reference → skip the move and say so in the
summary; a failed board write is reported, never fatal, and never a reason to
withhold a ready PR.

Do this in **both** modes. In merge-when-ready it is momentary — the merge closes
the issue and the board's own automation takes it to Done — but it is still
correct while the merge is in flight, and it is what the board shows if the merge
then fails.

- **Watch-only**: report "PR is ready" with a short summary (threads handled,
  checks green, branch up to date, issue moved to `In review`) and stop — wait
  for the user's explicit go-ahead before merging.
- **Merge-when-ready**: on ready, **capture the head branch name first** (you need it
  to delete the remote branch — `merge_pull_request` has no delete-branch option),
  merge with `mcp__github__merge_pull_request` (owner/repo from `origin`,
  `pullNumber: <n>`, `merge_method: squash`), **then** delete the remote branch, and
  report the result:

  ```bash
  git push origin --delete <branch>   # only after the squash-merge has landed
  ```

  On a 401/403, fall back to `gh pr merge --squash --delete-branch`.

**Several PRs queued?** Merge in dependency order. Stack one on another (base the
later PR on the earlier branch) **only** when they genuinely depend on each other
or the merge order is fixed up front — then merging the first leaves the next
already up to date, with no second rebase. Keep *independent* PRs on separate
`main`-based branches and rely on the rebase-before-ready rule above rather than
coupling unrelated work.

## Guardrails

- Never edit `.github/workflows/*` or other CI/config to force a check green, and
  never force-push, without surfacing to the user first.
- If a requested change is ambiguous or risky, reply on the thread with your
  assessment and leave it for the user (note it in the final summary) rather than
  guessing.
- **There is no pre-commit hook** (`.git/hooks/` holds only `*.sample`). Nothing
  lints on your behalf — run `/lint` before committing, or the PR's `Lint` job is
  the first thing that tells you.

Arguments: $ARGUMENTS
