pr-babysitter · diff
git:20260816.a11b45c to git:20260901.a877ac0
122 added, 144 removed. Audit A to A.
---
name: pr-babysitter
description: >-
- Monitors an open PR, detects it from the current branch, polls for conflicts,
- CI failures, review comments, and merge readiness, fixes safe issues, and
- reports only state changes. Monitor mode uses the Monitor tool when
- available, falling back to CronCreate/CronDelete; without either, run
- one-shot modes only. Use when asked to "babysit a PR",
- "watch this PR", "keep a PR green", "fix CI", "why is CI red", "resolve
- conflicts", "triage review comments", or "address PR comments". For PR
- creation use pr-creator; for diff review use pr-reviewer; for npm releases
- use autoship.
+ Keeps an open GitHub PR moving to merge: detects the PR from the current
+ branch, watches for merge conflicts, red CI (GitHub Actions, Buildkite,
+ Vercel, Fly.io), inbound review comments, and merge readiness, fixes what is
+ safe, and reports only state changes. Also runs one-shot: diagnose and fix
+ CI, rebase and resolve conflicts, or triage and answer review comments. Use
+ when asked to "babysit a PR", "watch this PR", "keep the PR green", "fix
+ CI", "why is CI red", "resolve conflicts", "rebase onto main", "address the
+ review comments", "reply to the reviewers", "is this PR ready to merge", or
+ "what is blocking the merge". For opening or editing the PR use pr-creator;
+ for reviewing the local diff use pr-reviewer; for applying a pr-reviewer
+ report use tidy; for npm release PRs use autoship.
---
# PR Babysitter
- - **IS:** autonomous monitoring of an open PR (conflicts, CI across GitHub Actions/Buildkite/Vercel/Fly.io, review comments, merge readiness) with auto-fixes, plus one-shot CI diagnosis or conflict resolution.
- - **IS NOT:** creating the PR (use `pr-creator`), reviewing the diff for bugs (use `pr-reviewer`), or npm release pipelines (use `autoship`, which watches its own release CI; never babysit a release or Version Packages PR that autoship is driving).
+ - **IS:** keeping one open PR moving: conflicts, CI across GitHub Actions/Buildkite/Vercel/Fly.io, inbound review comments, and merge readiness, as a background monitor or as one-shot fixes.
+ - **IS NOT:** opening or editing the PR (`pr-creator`), reviewing the diff for bugs (`pr-reviewer`), applying a local `pr-reviewer` report (`tidy`), or npm release PRs (`autoship` watches its own release CI; never babysit a release or Version Packages PR it drives).
## Mode Selection
| Invocation | Mode |
|------------|------|
- | "babysit", "watch this PR", "monitor", "keep it green" | Monitor: Phase 1 once, then phases 2-5 on every cron tick |
- | "fix CI", "why is CI red", "CI is broken", "loop on CI" | One-shot Phase 3 loop, no cron |
- | "resolve conflicts", "fix conflicts" | One-shot Phase 2, no cron |
- | "triage review comments", "address the comments" | One-shot Comment Triage Workflow, no cron |
+ | "babysit", "watch this PR", "monitor", "keep it green" | Monitor: Phase 1 once, then phases 2-5 on every event or tick |
+ | "fix CI", "why is CI red", "loop on CI" | One-shot Phase 3 loop |
+ | "resolve conflicts", "rebase onto main", "update the branch" | One-shot Phase 2 |
+ | "address the comments", "reply to the reviewers", "triage review comments" | One-shot Comment Triage Workflow |
+ | "is it ready", "what is blocking the merge" | One-shot Phase 5 report |
- Rules for every mode:
+ Standing rules, every mode:
- - No setup questions: auto-detect the PR, platforms, and defaults, start immediately. Overrides arrive inline only ("poll every 5 minutes", "enable auto-merge").
- - Watch mechanism ladder, checked at Phase 1: (1) Monitor tool available: start a background watch script that diffs PR state itself and emits a line only on transitions. (2) Else CronCreate/CronDelete available: cron polling every tick. (3) Neither: do not claim monitor mode is active. Run the matching one-shot mode, or tell the user this runtime cannot keep polling.
- - Skip closed or merged PRs. Skip drafts unless the user explicitly asks.
- - Comment triage runs autonomously inside the cycle, no plan approval gate.
+ - No setup questions. Auto-detect the PR, the CI platforms, and the defaults (poll every 2 minutes, auto-resolve noise, no auto-merge), then start. Overrides arrive inline: "poll every 5 minutes", "enable auto-merge".
+ - Skip closed or merged PRs. Skip drafts (`isDraft`) unless asked.
+ - Comment triage runs autonomously; the plan file is an audit trail, not an approval gate.
+ - Speak only on transitions. A quiet poll says nothing.
## Reference Files
| File | Read when |
|------|-----------|
- | `references/monitoring-setup.md` | Monitor start: watch script template, CronCreate fallback, state file format, defaults |
- | `references/merge-conflicts.md` | Phase 2: mergeStateStatus table, rebase workflow, auto-resolvable file types |
- | `references/ci-platforms.md` | Phase 3: per-platform log/retry commands, Buildkite auth fallback, failure classification, stale-dependency and `knip` handling |
- | `scripts/fetch-comments.sh` | Comment triage: run it first. Normalized JSON of every review, thread, and issue comment |
- | `references/github-api.md` | Comment triage: GraphQL and REST field sets, thread accounting, anchor recovery, awaiting-reply computation, review staleness, reply and resolve |
- | `references/bot-patterns.md` | Comment triage: unlisted-reviewer fallback, bot and human detection, severity mapping, merge-gate verdicts, deduplication, false positives |
- | `references/fix-plan-template.md` | Comment triage: audit-trail plan format |
- | `references/verification-gate.md` | Before any commit/push: lint, type-check, test, `knip` gate, stray-artifact sweep |
- | `references/git-resilience.md` | Any git command hangs or fails transiently (fsmonitor wedge, stale index.lock, IPC blip) |
+ | `references/monitoring-setup.md` | Phase 1: watch ladder detail, Monitor watch script, cron fallback, state file format, defaults |
+ | `references/merge-conflicts.md` | Phase 2: `mergeStateStatus` table, rebase workflow, lockfile and generated-file resolution, abort criteria |
+ | `references/ci-platforms.md` | Phase 3: `gh pr checks` fields and exit codes, per-platform log and retry commands, Buildkite auth chain, failure classification |
+ | `scripts/fetch-comments.sh` | Comment triage: run `${CLAUDE_SKILL_DIR}/scripts/fetch-comments.sh {N}` first. One JSON document of every review, thread, and issue comment; `--help` prints the output shape |
+ | `references/github-api.md` | Comment triage: script output contract, manual GraphQL/REST fallback, thread accounting, anchor ladder, awaiting-reply rule, reply and resolve |
+ | `references/bot-patterns.md` | Comment triage: reviewer detection, severity mapping, merge gates, noise markers, dedup, false positives |
+ | `references/fix-plan-template.md` | Comment triage: plan file format and the legal ignore reasons |
+ | `references/verification-gate.md` | Before any commit: lint, type-check, test, `knip`, stray-artifact sweep |
+ | `references/git-resilience.md` | A git command hangs or fails transiently (fsmonitor wedge, stale `index.lock`, network blip) |
+ | `evals/evals.json` | Only when changing this skill; never during a PR task |
## Monitor Loop
- Phase 1 runs once in the foreground and starts the watch. With the Monitor tool, the watch script gates on transitions in the background: quiet polls never wake the agent, and each emitted event runs phases 2-5. With the cron fallback, every tick runs phases 2-5 and diffs against the previous tick's state file. Either way, only transitions produce output; a quiet poll says nothing.
+ Phase 1 runs once in the foreground and starts the watch. Every event or tick then runs phases 2-5, diffs against the state file, and speaks only when something changed.
Copy this checklist to track progress:
- ```
+ ```text
PR babysit progress:
- - [ ] Phase 1: Initialize (auto-detect PR, snapshot state, start cron)
- - [ ] Phase 2: Conflict check (detect and resolve merge conflicts)
- - [ ] Phase 3: CI/CD check (poll checks, diagnose failures, fix and push)
- - [ ] Phase 4: Comment check (detect new comments, triage autonomously)
- - [ ] Phase 5: Readiness check (evaluate merge readiness, notify user)
+ - [ ] Phase 1: Initialize (detect PR, pick watch mechanism, snapshot state)
+ - [ ] Phase 2: Conflict check
+ - [ ] Phase 3: CI check (diagnose, fix, gate, push)
+ - [ ] Phase 4: Comment check (triage new comments)
+ - [ ] Phase 5: Readiness check (report transitions, write state file)
```
### Phase 1: Initialize
- Load `references/monitoring-setup.md` for the watch script template, CronCreate fallback config, and defaults.
+ 1. `gh pr view [N] --json number,url,title,state,isDraft,headRefName,baseRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision`. No PR for the branch: say so and stop.
+ 2. `gh repo view --json owner,name` for the calls that need `owner/repo`.
+ 3. Detect CI platforms from `gh pr checks --json name,link` (dispatch table in Phase 3).
+ 4. Pick the watch mechanism: first rung that applies.
- 1. **Auto-detect the PR**: `gh pr view --json number,url,title,headRefName,baseRefName,mergeable,mergeStateStatus,reviewDecision`. If a PR number was passed, use it. No PR for the branch: say so and stop.
- 2. **Extract owner/repo**: `gh repo view --json owner,name`
- 3. **Detect CI platforms** from `gh pr checks` check names (dispatch table in Phase 3)
- 4. **Start the watch**: prefer the Monitor tool with the watch script from `references/monitoring-setup.md` (`persistent: true`); fall back to CronCreate with `*/2 * * * *` running phases 2-5. Capture the watch/job ID.
- 5. **Snapshot state** to `.claude/scratchpad/babysit-pr-{N}.md`: watch mechanism and ID, HEAD SHA, mergeable status, check statuses, unresolved thread count, review decision.
- 6. **Print confirmation**:
+ | Rung | Available when | Behaviour |
+ |------|----------------|-----------|
+ | Harness PR subscription | A PR-subscription tool is exposed (cloud sessions: `Claude_Code_Remote:subscribe_pr_activity`; GitHub MCP server: `github:subscribe_pr_activity`), or the web session's Auto-fix toggle is on | GitHub pushes review comments, CI failures, and check-suite success into the session. GitHub sends nothing when the base branch advances, so pair it with a slow Monitor poll (10 minutes) on `mergeStateStatus`. If the tool reports a PR Steward already watching, this session gets no events: say the PR is already covered and offer the one-shot modes instead of double-fixing |
+ | Monitor tool | `Monitor` is in the tool list | Start the watch script from the monitoring reference with `persistent: true`. Quiet polls never wake the agent; each emitted line runs phases 2-5 |
+ | Cron | `CronCreate` is in the tool list | `*/2 * * * *` running phases 2-5; every tick wakes the agent. Recurring tasks expire after 7 days |
+ | None | Neither | Do not claim monitor mode. Run the matching one-shot mode, or say this runtime cannot keep polling |
- ```
- Monitoring PR #{N}: {title}
- Polling every 2 minutes | Auto-resolve noise: yes | Auto-merge: no
- Detected CI: {platforms}
- Watch: {monitor|cron} ({id})
- Current state: {mergeable} | {reviewDecision} | {check_summary}
- ```
+ 5. Snapshot state to `.claude/pr-babysitter/babysit-pr-{N}.md`: mechanism and ID, head SHA, mergeability, check states, open and awaiting-reply thread counts, review decision. This folder is never staged.
+ 6. Confirm in five lines: PR, watch mechanism and ID, detected CI, current state, defaults in effect.
### Phase 2: Conflict Check
- Load `references/merge-conflicts.md` for the mergeStateStatus table and resolution strategy.
+ `gh pr view --json mergeable,mergeStateStatus`. `DIRTY` resolves; `BEHIND` updates; `UNKNOWN` means GitHub is still computing, recheck next tick; anything else moves on.
- 1. **Check mergeable**: `gh pr view --json mergeable,mergeStateStatus`
- - `MERGEABLE`, up to date → skip to Phase 3
- - `CONFLICTING` → resolve
- - `UNKNOWN` → GitHub still computing; recheck next tick
- 2. **Rebase**: `git fetch origin {base_branch} && git rebase origin/{base_branch}`
- - clean → `git push --force-with-lease` → notify
- - conflicts only in safe files (lockfiles, generated, changelogs) → auto-resolve per the reference, push
- - logic conflicts in source → `git rebase --abort` → notify with the conflicting files and each side's change
+ ```bash
+ git fetch origin {base} && git rebase origin/{base}
+ git push --force-with-lease --force-if-includes
+ ```
- Never push with bare `--force`. A failed `--force-with-lease` means someone else pushed: abort and notify, do not overwrite their commits. If `git fetch` or `git rebase` hangs, see `references/git-resilience.md`.
+ - Clean rebase: push, notify.
+ - Conflicts only in lockfiles, generated files, or changelogs: regenerate per the reference, continue the rebase, push.
+ - Conflicts in source logic, migrations, or API contracts: `git rebase --abort`, then notify with the files and what each side changed. Human intent decides those.
- ### Phase 3: CI/CD Check
+ Bare `--force` is never used. A refused lease means someone else pushed: abort and notify rather than overwrite their commits. More than one author on the branch means a rebase rewrites their commits: merge `origin/{base}` instead.
- Load `references/ci-platforms.md` for per-platform commands, the Buildkite auth fallback chain, and the failure-classification decision tree.
+ ### Phase 3: CI Check
- 1. **Poll**: `gh pr checks --json name,state,conclusion,detailsUrl`
- 2. **Classify each check**: passing, pending (wait for completion before diagnosing), or failing
- 3. **All passing** → proceed to Phase 4
- 4. **Failing** → dispatch on check name to fetch logs:
+ 1. `gh pr checks --json name,state,bucket,link,workflow`. `bucket` is `pass`, `fail`, `pending`, `skipping`, or `cancel`; `link` is the details URL.
+ 2. Anything `pending`: wait. Diagnosing a half-finished run fixes the wrong thing.
+ 3. Every `fail`: fetch logs by platform.
- | Check name / detailsUrl | Platform | Failure logs via |
- |-------------------------|----------|------------------|
- | `buildkite/` prefix | Buildkite | Auth fallback chain: `bk` CLI, then REST API, then detailsUrl |
- | `vercel` in name or `vercel.com` in URL | Vercel | `vercel logs {deployment_url}` |
- | `fly-` prefix or `fly.io` in URL | Fly.io | `flyctl logs --app {app_name} --no-tail` |
+ | Check `name` or `link` | Platform | Logs |
+ |------------------------|----------|------|
+ | `buildkite/` prefix | Buildkite | `bk` CLI when `bk auth status` passes, else REST with `BUILDKITE_API_TOKEN`, else hand over the `link` |
+ | `vercel` in name or `vercel.com` in link | Vercel | `vercel inspect --logs {deployment_url}` (build logs; `vercel logs` is runtime) |
+ | `fly-` prefix or `fly.io` in link | Fly.io | `flyctl logs --app {app} --no-tail` |
| Anything else | GitHub Actions | `gh run view {run_id} --log-failed` |
- 5. **Classify the failure** per the decision tree: flaky (re-run), stale dependency (reinstall/rebuild before touching source), code error (fix), `knip` (remove dead code or configure), infrastructure (notify; not fixable from code)
- 6. **Fix, gate, push**: run the verification gate (`references/verification-gate.md`) locally before pushing
- 7. **Compare with previous state**: flag regressions (was passing, now failing)
+ 4. Classify per the reference: flaky (re-run once), stale dependency (reinstall and rebuild before touching source), code error (fix), `knip` (delete dead code or configure), infrastructure (notify; not fixable from code).
+ 5. Fix, run the verification gate, commit, push. Flag regressions against the previous state (was passing, now failing).
- **One-shot loop ("fix CI"):** after pushing, run `gh pr checks --watch`; re-diagnose if still red. Exit when checks go green (report it), the failure is infrastructure, or the same check fails twice with the same error after a fix; then summarize instead of thrashing.
+ **One-shot loop ("fix CI"):** after each push, `gh pr checks --watch --fail-fast` (exit 0 green, 1 a check failed, 8 still pending). Stop and summarize when checks are green, the failure is infrastructure, or the same check fails twice with the same error after a fix. Two identical failures is the signal to stop pushing, not to try a third variant.
### Phase 4: Comment Check
- 1. **Count two numbers**: open threads, and threads awaiting my reply (newest comment is not mine, in any resolution state, minus a reviewer who resolved their own last comment)
- 2. **Compare with the state file** on both counts plus the newest comment timestamp across review and issue comments. An edited-in-place bot comment and a reply on a resolved thread both have to register
- 3. **Any increase** → notify "N new review comments on PR #{N}", then run the Comment Triage Workflow
- 4. **Auto-resolve noise** only on a positive marker match, with a one-line reason. Never auto-resolve human comments, questions, critical or major findings, or merge-gate comments
+ 1. Count open threads and threads awaiting my reply (newest comment not mine, in any resolution state, minus a reviewer who resolved their own last comment).
+ 2. Compare both counts and the newest `updated_at` across review and issue comments against the state file. An edited-in-place bot comment and a reply on a resolved thread both have to register.
+ 3. Any increase: notify "N new review comments on PR #{N}" and run the Comment Triage Workflow.
### Phase 5: Readiness Check
- 1. **Ready** = all of: `mergeable == MERGEABLE`, all required checks passing, `reviewDecision == APPROVED` from a review whose `commit_id` is the head SHA, zero open blocking threads, zero threads awaiting my reply, every merge gate satisfied
- 2. **Merge gates**: an auto-approval assessment reading "Human review required" means this PR will not be auto-approved. Report it as a blocker naming the path criteria that forced it and the human review needed. Never treat it as a finding to fix
- 3. **Ready** → notify: "PR #{N} is ready to merge. All checks green, reviews approved, no conflicts." Do not merge; auto-merge requires explicit opt-in
- 4. **Not ready** → report blockers: "Waiting on: 2 checks pending" / "Blocked by: merge conflict" / "Awaiting your answer: {Q} questions from @{reviewer}" / any stale approval
- 4. **Notify only on transitions**: check went green/red, new review, conflict appeared/cleared, all clear
- 5. **Write the state file** for the next tick to diff against
+ 1. Ready means all of: `mergeable == MERGEABLE`, every required check `pass`, `reviewDecision == APPROVED` from a review whose `commit_id` is the head SHA, zero open blocking threads, zero threads awaiting my reply, every merge gate satisfied.
+ 2. A merge-gate comment reading "Human review required" is a blocker to report with the criteria that forced it, not a finding to fix.
+ 3. Ready: notify "PR #{N} is ready to merge." Merge is a one-way door: `gh pr merge --auto` with the repo's merge method, and only when the user opted in.
+ 4. Not ready: name the blockers ("Waiting on: 2 checks pending", "Awaiting your answer: 2 questions from @reviewer", "Approval is stale: reviewed abc1234, head def5678").
+ 5. Write the state file for the next tick.
## Comment Triage Workflow
- Runs inline when Phase 4 finds comments, or one-shot when invoked directly. No plan approval; the plan file is an audit trail.
-
- Load `references/github-api.md` for query templates and `references/bot-patterns.md` for detection rules.
+ Inline from Phase 4 or one-shot. Autonomous: no approval gate, the plan file is the audit trail.
### Fetch
- Run `scripts/fetch-comments.sh {N}` first: it emits normalized JSON for every review, thread, and issue comment, with recovered anchors, thread buckets, and `owedReply` already computed. If `bash`, `jq`, or `gh` is unavailable or it exits non-zero, fall back to the queries in the API reference and do these steps by hand.
+ Run `${CLAUDE_SKILL_DIR}/scripts/fetch-comments.sh {N}`. It pages every thread and every thread's comments, recovers anchors, buckets threads, and computes `owedReply` against your own login. A non-zero exit prints one sentence on stderr saying why; fall back to the manual queries in the API reference only when `gh` or `jq` cannot be installed.
- 1. **Identify yourself**: `gh api user --jq .login`. Every reply-owed decision compares against this; `viewerDidAuthor` is unreliable
- 2. **Reviews first**: REST reviews with `state`, `body`, `user.login`, `commit_id`. Keep the reviewer set: every reviewer must be accounted for in the output
- 3. **All review threads**: paginated GraphQL with the full field set. Do not filter on `isResolved`. Bucket every thread (open, resolved with an unanswered reply, resolved and quiet, PR-level) and keep all the counts
- 4. **Page truncated threads**: any thread with `comments.hasNextPage` is re-fetched. Thread comments come oldest first, so the reply you owe is the one a truncated page hides
- 5. **Recover anchors**: for every thread with a null `line`, walk the anchor ladder. A null `line` means outdated or multi-line, not PR-level
- 6. **Issue-level comments**: REST conversation comments. Compare `updated_at`, not just `id`: gate and Danger bots edit one comment in place
- 7. **Early exit** only when all of these are zero: open threads, threads awaiting my reply, actionable reviews, actionable issue comments. Plus every reviewer accounted for
+ Check `reviewers[]` before classifying: every login that spoke must appear in the output with findings, a verdict, or an explicit "no content". A reviewer with reviews but zero comments is a fetch that lost something. `anchor.source == "needs-translation"` means finish the anchor ladder against the working tree before judging that finding.
- ### Classify
+ Early exit only when open threads, awaiting-reply threads, actionable reviews, and actionable issue comments are all zero.
- 1. **Read every inline comment from every author.** A reviewer absent from the bot table is unknown, not noise: triage it as an active reviewer. Noise requires a positive marker match
- 2. **Classify per comment, not per thread**: a human reply inside a bot's thread is a human comment with full human weight
- 3. **Author type**: content first, then username. `github-actions[bot]` is shared; a `[bot]` suffix and `__typename` are hints, not verdicts
- 4. **Severity**: parse the source's markers; unknown sources default to Major
- 5. **Intent** for human comments: fix request, question, nitpick, or acknowledgement. A question gets an answer, not a code change
- 6. **Merge gates**: an auto-approval or merge-freeze verdict is a readiness-check input. Record it, never fix it, never resolve it
- 7. **Deduplicate bots only**: same path within 3 lines, keep the highest severity. Never across the human boundary; a multi-location finding is one item
- 8. **Disposition**: fix, answer, or ignore with a stated reason. There is no ignore reason for "author unrecognized" or "thread already resolved"
+ ### Classify
- Human comments are never auto-ignored. Classify as fix unless already resolved or the reviewer marked it optional.
+ - Every inline comment from every author is read. An author absent from the bot table is unknown, not noise; noise needs a positive marker match.
+ - Classify per comment, not per thread: a human reply inside a bot's thread carries full human weight.
+ - Author type from content first, then login. `github-actions[bot]` is shared by reviewers and noise alike.
+ - Severity from the source's own markers; unknown sources default to Major. Severity orders the queue; it never decides whether a comment is read.
+ - Human intent: fix request, question, nitpick, or acknowledgement. A question gets an answer, not a code change. Human comments are never auto-ignored: fix unless the reviewer marked it optional.
+ - Merge-gate verdicts are Phase 5 inputs: record, never fix, never reply, never resolve.
+ - Deduplicate bots only (same path within 3 lines, keep the highest severity). A multi-location finding is one item.
+ - Every ignore carries one of the legal reasons from the plan template. "Author unrecognized" and "thread already resolved" are not among them.
### Fix
- 1. **Write the plan** to `.claude/scratchpad/pr-{N}-review-plan.md` per `references/fix-plan-template.md` (the audit trail)
- 2. **Print counts** (N to fix, Q questions to answer, K conversation items, M ignored, plus the thread buckets) and proceed immediately
- 3. **Resolve ignored threads**: brief reply, then resolve via GraphQL
- 4. **Answer questions**: post the reply, leave the thread unresolved. The reviewer resolves it once the answer lands
- 5. **Reply on already-resolved threads with an unanswered human reply**: reply in place, do not unresolve, note it in the report
- 6. **Fix real issues** grouped by commit group; parallelize independent file fixes
- 7. **Gate, commit, push**: the verification gate (`references/verification-gate.md`) must pass; sweep stray artifacts (e.g. a root `schema.gql` from a hook); one commit per logical group, staging only that group's files
- 8. **Reply and resolve** each fixed thread via GraphQL
- 9. **Verify**: re-fetch threads; report open threads, threads still awaiting my reply, and questions answered but not yet acknowledged, plus current CI status
+ 1. Write the plan (`references/fix-plan-template.md`) to `.claude/pr-babysitter/pr-{N}-review-plan.md`, print the counts, proceed.
+ 2. Ignored threads: one-line reply, then resolve.
+ 3. Questions: post the answer, leave the thread open. The reviewer resolves it.
+ 4. Resolved threads with an unanswered human reply: reply in place, do not unresolve, note it in the report.
+ 5. Fixes, one commit per logical group. Run the verification gate before each commit and stage only that group's files.
+ 6. Reply, then resolve, each fixed thread.
+ 7. Re-run the script and report: open threads, threads still awaiting my reply, questions answered but not yet acknowledged, and current CI status. The re-run is the evidence; "addressed everything" is not.
## Stopping
- - "Stop babysitting" / "cancel the PR monitor" → cancel the watch using the mechanism and ID from the state file: TaskStop for a Monitor watch, CronDelete for the cron fallback
- - PR merged or closed → the Monitor script emits a terminal event and exits; cron detects it on the next tick and self-cancels
- - Session exit → watches and jobs are session-scoped, auto-clean
+ - "Stop babysitting" / "cancel the monitor": read the mechanism and ID from the state file. Monitor watch: `TaskStop`. Cron: `CronDelete`. Harness subscription: the matching unsubscribe tool.
+ - PR merged or closed: the watch script emits `TERMINAL` and exits; cron detects it next tick and self-cancels.
+ - Session exit: watches and cron jobs are session-scoped and clean themselves up. Background tasks are not restored on `--resume`; re-run the skill.
- On stop, report a final summary: total polls, fixes applied, conflicts resolved, comments triaged, current state.
+ On stop, report: polls or events handled, fixes applied, conflicts resolved, comments triaged, current state.
## Gotchas
- - Skipping a reviewer because it is not in the bot table: unlisted reviewers are the ones posting High-severity bugs. Absent means unknown, not noise.
- - Triaging a bot's review body instead of its inline comments: the body is a count, the findings are inline. Codex puts every finding in threads.
- - Filtering threads on `isResolved == false`: a resolved thread with a reply after the resolve is the comment most likely to be missed, and GitHub collapses it out of sight.
- - Treating a null `line` as no location: null means outdated or multi-line. Recover the anchor before deciding anything.
- - Reading only the first page of a thread's comments: thread comments come oldest first, so the reply you owe is the one you cannot see.
- - Trusting `viewerDidAuthor` to spot your own comments: it returns false even on your own PR. Compare against `gh api user --jq .login`.
- - Answering a reviewer's question with a code change: a question wants an answer. Change code only if the honest answer is that it is broken.
- - Resolving a thread where you answered a question: only the reviewer knows whether the answer landed.
- - Reporting "no comments" because every review body was empty: humans put everything inline. Four empty-body reviews are one review pass with all its content in threads.
- - Treating an auto-approval verdict as a finding or as noise: "Human review required" is a merge gate for Phase 5, not something to fix or resolve.
- - Counting a stale approval as approval: a review whose `commit_id` is not the head SHA may be dismissed by branch protection.
- - Auto-resolving or auto-ignoring human comments: reviewers re-open them and lose trust. Humans classify as fix unless marked optional.
- - Resolving a thread without a reply first: the reviewer sees a silent resolve and unresolves it.
- - Fixing items the triage classified as ignore: churn nobody asked for; contradicts the audit trail.
- - One commit per individual comment: unreadable review history. Group related fixes by commit-group label.
- - Committing stray hook artifacts (e.g. a root `schema.gql`): pollutes the PR diff. Sweep `git status --porcelain`, stage only the fix's files.
- - Treating a monorepo type-check failure as a code bug: often stale deps or generated types. Reinstall and rebuild first; edit source only if it persists.
- - Aborting the monitor on one hung or transient git command: fsmonitor wedges and stale locks are recoverable (`references/git-resilience.md`). Retry first.
- - Re-diagnosing while checks are still pending: you fix the wrong thing on a half-finished run. Wait for completion.
- - Polling faster than every 2 minutes: burns GitHub API rate limit for no signal. 2 minutes is the floor.
- - Using cron when the Monitor tool is available: every quiet tick wakes the agent and burns tokens. The Monitor script diffs in the background; only transitions wake the agent.
- - Auto-merging without explicit opt-in: merge is a one-way door. "Ready to merge" is a notification, not an action.
- - Classifying `github-actions[bot]` as always noise: shared identity used by DangerJS, schema checkers, and other reviewers. Classify by content.
- - Using `bk` CLI without checking `bk auth status` first: Keychain tokens expire; a dead token stalls the cycle. Fall back to the REST API or `gh pr checks`.
+ - `gh pr checks --json name,state,conclusion,detailsUrl` errors: `conclusion` and `detailsUrl` belong to `gh pr view --json statusCheckRollup`. The check fields are `bucket` and `link`.
+ - Filtering threads on `isResolved == false`: GitHub collapses resolved threads, so a human reply posted after the resolve is the comment most likely to go unread.
+ - `comments(first: 20)`: thread comments arrive oldest first, so a truncated page hides the newest comment, the one that decides whether you owe a reply. The script pages every thread to the end.
+ - `viewerDidAuthor` returned `false` on the viewer's own comments. Compare `author.login` to `gh api user --jq .login`.
+ - A null `line` means outdated or multi-line, not PR-level. Only a null `path` is PR-level. Recover the anchor before deciding anything.
+ - Triaging a bot's review body: the body is a count. Codex, Devin, Copilot, and Bugbot all put findings inline. Four empty-body human reviews are one review pass with its content in threads, not four reviewers with nothing to say.
+ - Bots that edit one comment in place (auto-approval assessments, DangerJS) keep the same `id`, so a state diff on ids sees nothing. Compare `updated_at`.
+ - Counting a review whose `commit_id` is not the head SHA as an approval: branch protection with "dismiss stale approvals" drops it on the next push, and the PR reads ready until then.
+ - `vercel logs {url}` streams runtime logs. A failed build lives in `vercel inspect --logs {url}`.
+ - `bk` without `bk auth status` first: keychain tokens expire and a dead token stalls the cycle. Fall through to REST, then the check `link`.
+ - A monorepo type-check failure pointing into a sibling package's `dist` is usually stale build output. Reinstall and rebuild before editing source.
+ - `git add -A` after a fix commits hook artifacts (a root `schema.gql`) into the PR. Sweep `git status --porcelain` and stage paths.
+ - Resolving a thread without replying first: the reviewer sees a silent resolve and unresolves it.
+ - A subscription-only watch never sees conflicts: GitHub emits no webhook when the base branch advances into one.
+ - Cron when Monitor is available wakes the agent on every quiet tick and burns tokens for no signal. Polling under 2 minutes does the same to the GitHub rate limit.
## Related Skills
- - `pr-creator`: opens the PR; babysitting starts after it exists
+ - `pr-creator`: opens or edits the PR; babysitting starts after it exists
- `pr-reviewer`: local diff review for bugs; run it on monitor-authored fixes beyond a trivial patch
+ - `tidy`: applies a `pr-reviewer` report to the working tree; this skill applies GitHub review comments
- `autoship`: npm release pipelines; it watches its own release CI, so never babysit a release PR it drives