--- 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: :`, `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 `** — **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 `** — **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: `) — 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: `), 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: `) 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: `). "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: `; 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: `, `merge_method: squash`), **then** delete the remote branch, and report the result: ```bash git push origin --delete # 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