# Babysit Prs

> Babysit your own open GitHub pull requests as a tiered fleet loop. The safe default discovers YOUR PRs under the current repo's owner, checks readiness, fixes clear branch-owned issues, and reports, it never resolves threads or merges. Explicit 'worker' tier adds auto-resolving outdated bot threads and gate-proven merges; explicit 'autopilot' adds all authors under the watched owners. Use when asked to babysit, watch, or advance open pull requests as a fleet (the safe tier), to run the worker or autopilot tier by name, or when pairing with /loop for continuous coverage; not for the single-PR lifecycle: prep, create, monitor one PR, or merge (use /pull-request).

- Skill: `melodic-software/babysit-prs` (Agent Skill, multi-file: 58 files)
- Install (CLI): `npx skillmds@latest add melodic-software/babysit-prs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/melodic-software/babysit-prs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: melodic-software (https://skillmd.com/u/melodic-software)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/melodic-software/babysit-prs

---


## Pre-computed context

Current login: !`gh api user --jq .login 2>/dev/null || echo "unknown"`
Own open PRs here: !`gh pr list --state open --author "@me" --limit 200 --json number --jq 'length' 2>/dev/null || echo "unknown"`

Branch and working tree: gather with two separate Bash calls, `git branch --show-current` then `git status --porcelain`; treat a failure as an unknown value and carry on. Keep them out of the pre-computed block above: the harness composes that block into one shell invocation, and a worktree-isolated agent refuses a git-bearing compound command. The dated record for that composition claim is the `worktree` skill's [reference/gather-block.md](../worktree/reference/gather-block.md), "The pre-compute block runs as one shell invocation".

## Purpose

Keep pull requests moving without taking unsafe GitHub actions. Guarantees are enforced in
deterministic gate scripts; judgment stays with the agent. The safe default discovers your own
open PRs, author is one of your self logins, under the current repo's owner (or the configured
watched owners), works each to readiness, and reports. **The safe tier never resolves threads
and never merges**; merging exists only behind the explicit `worker`/`autopilot` opt-in and a
deterministic merge gate. Designed for `/loop /source-control:babysit-prs` continuous coverage.

The per-PR review discipline (finding extraction, per-finding D1–D7 verification gates,
self-reply filtering) is the plugin-scope seam shared with `/source-control:pull-request`:
[`${CLAUDE_PLUGIN_ROOT}/reference/review-discipline.md`](../../reference/review-discipline.md).
Read it before processing findings; dispatched workers cite it directly.

## Modes and arguments

An invocation is `[mode] [scope]`; mode and scope are orthogonal. Combine them freely (`worker owner/repo`, `worker #87`, etc.).

| Mode | What it does |
| --- | --- |
| *(none)*, the configured `default_tier` (`safe` unless the consumer changed it) | Safe tier: discover in-scope PRs (your own, under the current repo's owner by default), check readiness, fix clear branch-owned issues and push, report blockers and the next cadence. Never resolves threads or merges. |
| `worker` | Everything safe does, and additionally auto-resolves *pre-push-outdated* bot threads and merges PRs the merge gate proves 100% ready. Requires Python. For dedicated 24/7 loops over your own PRs. |
| `autopilot` | Maximum autonomy for a solo owner: every open PR under the watched owners regardless of author; fix everything it can; resolve every thread it has addressed (bot, AI-review, and human); merge everything the gate proves ready. Requires Python. A deliberate power-user opt-in, never a default. |
| `help` (or `?`) | Print the common flows and the effective configuration below; take no other action. |

**Tier selection is explicit.** The tier keyword in the invocation wins. An explicitly typed
`/source-control:babysit-prs` invocation with no keyword, including inside `/loop`. Runs the
configured `default_tier`, `safe` unless the consumer changed it. An auto-routed match (this
skill loaded from conversational vocabulary such as "babysit my PRs" rather than a typed
invocation) always runs the safe tier: `default_tier` never acts on auto-routed invocations, so
a merge-capable tier engages only when the user names it, the `worker`/`autopilot` keyword, or
a typed invocation under a deliberately changed `default_tier`. Configuration can never convert
a casual invocation into standing merge authority.

Any tier also honors an explicit user instruction to merge or resolve specific PRs now; that is a direct order, not autonomous behavior, and it runs the same guarded gates below.

Common flows (this is what `help` prints, along with the effective configuration):

```text
/source-control:babysit-prs                     one pass at the configured default tier
/source-control:babysit-prs #87                 one PR in the current repo
/source-control:babysit-prs owner/repo          one repository
/source-control:babysit-prs owner/repo#87       one specific PR
/source-control:babysit-prs worker              full-auto (your PRs): fix + resolve-outdated + merge-ready
/source-control:babysit-prs autopilot           max autonomy: ALL authors, fix+resolve+merge, escalate stuck PRs
/source-control:babysit-prs help                list these flows and stop
Looped worker:    /loop 15m /source-control:babysit-prs worker
Looped autopilot: /loop 15m /source-control:babysit-prs autopilot
Explicit order (any tier): "merge owner/repo#87 now" | "resolve bot threads on #87"
```

## Scope resolution

Scope resolves deterministically, most specific first. Read the current git context with `gh`/`git` (all read-only) before falling back:

1. **Explicit full ref** in the invocation (`owner/repo#N` or `owner/repo`), use it exactly.
2. **Bare PR number** (`#87`, `pr 87`) inside a git repo, resolve the current repo with
   `gh repo view --json nameWithOwner -q .nameWithOwner` and target that `owner/repo#87`.
3. **Bare invocation inside a repo whose current branch has an open PR you authored**. Target
   just that one PR (detect with `gh pr view --json number,url,author,headRefName`; use it only
   when the PR exists and its author is a self login).
4. **Bare invocation inside a repo under a watched owner**, that repository's own open PRs.
   When `watched_owners` is unset, the current repo's owner is the inferred watch scope.
5. **Otherwise** (a neutral working directory, or an unattended loop), your own open PRs
   across every watched owner, via the snapshot's author filter.
6. **Conversation context** that clearly scopes specific PRs or repositories overrides the
   working-directory inference at any level.

Own-authorship is the default safety boundary, not a preference: never act on another person's
PR under the safe default. Widen beyond your own authorship ONLY on an explicit user
instruction or in `autopilot`, a deliberate opt-in that drops the author filter. The owner
allowlist (`watched_owners`, inferred as the current repo's owner when unset) is a separate,
always-on trust boundary: even autopilot never acts on a repository outside the watched owners.

## Autonomy tiers (per action class)

Autonomy is decomposed per action, not per run. Irreversibility governs the gate.

| Action class | Safe (default) | Worker | Autopilot |
| --- | --- | --- | --- |
| Discover, snapshot, report | yes | yes | yes, **all authors** |
| Fix clear branch-owned CI or bot-review issues, commit, push | yes | yes | yes, and harder (research a fix before giving up) |
| Dispatch a dedicated conflict worker (`git merge`, never rebase; it resolves locally and never pushes, the orchestrator re-verifies and pushes, [reference/orchestration.md](reference/orchestration.md)) | no, report (simple mechanical conflicts met while freshening a branch are still handled inline per [reference/loop.md](reference/loop.md)) | mechanical/textual conflicts only, escalate genuine ambiguity | mechanical/textual conflicts only, escalate genuine ambiguity |
| Resolve review threads | no, report | **pre-push-outdated bot threads only** | any thread **it has addressed**, bot, AI-review, or human |
| Merge a PR | no. Report readiness | only when the gate proves 100% ready | only when the gate proves 100% ready |
| Mark a completed draft ready (`gh pr ready`) | no, report | no, report | yes, via its worker's completeness assessment |
| Refresh a stale (behind-base) branch, post a review trigger | orchestrator-only | orchestrator-only | orchestrator-only |
| `CHANGES_REQUESTED`, security/P1, posture, design, dependency acceptance | escalate | escalate | attempt with research; escalate only when it cannot confidently and safely resolve |

**Reading the merge-conflict blocker string.** The snapshot classifier is mode-agnostic by
design, it has no tier input, so it emits the same blocker string, `"merge conflict;
dedicated conflict-resolution agent required"`, for every conflicting PR regardless of tier.
That string names a capability that exists in this skill, not an instruction to invoke it. In
the safe tier the table's `no — report` still governs: report the blocker exactly as stated
and dispatch no conflict worker. Only `worker` and `autopilot` read that same string as
license to act.

**Cross-tier invariants**, hold in every tier including autopilot: never a force-push (freshness is
merge-only, refspec-pushed fast-forward, [reference/loop.md](reference/loop.md)); never `--admin`;
never delete a branch or worktree
that is dirty or unmerged; never change GitHub settings, secrets, branch protection, or
billing; never act on a repository outside the watched owners; never resolve a thread whose
finding is not actually addressed. A merge always requires the deterministic gate. Autopilot
works harder to reach that state but never forces past it. A blocked action escalates; it is
never routed around. Advisory-only fix attempts are bounded per PR (the fix-round cap in
[reference/orchestration.md](reference/orchestration.md)); blocking defects are never capped.
**Dependency hold-merge:** a dependency-manager-authored PR (Dependabot/Renovate-class) is
never merged autonomously in ANY tier, the merge gate refuses it absent `--allow-dependency`,
which is passed only on an explicit user instruction to merge that specific PR.

**Draft policy (per tier).** Drafts enter evaluation scope in every tier. There is no blanket
draft skip. Safe: evaluate and report draft status, never `gh pr ready`. Worker and autopilot:
zero-blocker drafts always route through a worker (see Fan out). `gh pr ready` happens only in
autopilot, only for a draft its worker assesses complete.

## Autopilot

`autopilot` is a deliberate, set-aside power-user tier for a **solo owner** who wants the queue
driven to zero, not the default, and not for a repo with other human reviewers whose feedback
must not be steamrolled. It processes every PR, fixes what it can, resolves only threads it has
addressed, merges through the pinned gate, and escalates the specific PRs that genuinely need a
human. What it does per PR, what "every PR" excludes, its draft-PR handling, and which scopes it
widens are the single home in [reference/autopilot.md](reference/autopilot.md).

## Autopilot merge tier

A config-gated escalation of autopilot's merge authority, off by default and active only while the operator sets `babysit_autopilot_merge_tier`; with the key unset, every merge decision follows the base path above. When enabled, per candidate PR autopilot runs a **genuine review pass** under a **second bot account** (author ≠ approver) that submits an approving review **only when clean**, then runs the pinned merge gate with the `--autopilot-merge-tier` flags layered onto `--merge --expected-head <post-push-head-sha>`. The concrete enabled-path merge command, the second-account approve mechanic, and the review-workflow requiredness precondition for enabling the tier are the single home in [reference/safety.md](reference/safety.md).
That gate merges **only when every criterion holds**, the criteria and the safety-contract rationale are codified in [reference/safety.md](reference/safety.md). It is **fail-closed** (the umbrella flag refuses unless all three parameter sets are supplied; predicates reused from the shared `babysit_classify` module), and any criterion failing falls back to the human merge-ready list, the tier never routes around the gate.

## Guarded mutations: deterministic gates, agent judgment

The two mutation gates are invoked ONLY through their wrapper scripts, by the bundled `bin/`-path form,
never the bare command name nor the raw Python behind them. Each `source-control-babysit-<x>` named in the bullets below is that wrapper launched by its `bin/`-path form; the exact form is the single
home in [reference/safety.md](reference/safety.md). Both fail closed without `--allowed-owners`.

**Before composing either wrapper command, read [reference/safety.md](reference/safety.md),
"Guarded Mutation Wrappers", for the exact flag set.** That section is the single home for the
`bin/`-path form, every configured flag that must ride on a given form (self logins, extra bot
logins, the review-settle pair, extra dependency-manager logins), the pins and the refusals they
produce, the override flags that are never passed autonomously, and the per-thread `action`
vocabulary. Never reconstruct one of these commands from memory; a silently dropped flag is a
silently dropped guard.

- **Merge readiness**. `source-control-babysit-merge` is the gate. Safe tier runs it read-only and
  reports; `worker` and `autopilot` add `--merge --expected-head <vetted-head-sha>` only on a
  vetted head, and an enabled autopilot merge tier layers its own flags on
  ([reference/safety.md](reference/safety.md)). When the expected-head pin is missing, or the
  pinned sha no longer matches the live head, the wrapper refuses: re-snapshot and reassess the new
  head instead of using `--allow-unpinned-head`. React
  to the reported `blockers`; do not bypass the gate, and never reach for one of its override flags
  on an unattended path. **This gate's `ready` field is the sole authority for calling a PR merge-ready**, never the finding-classification gate's `READINESS_OK` ([reference/safety.md](reference/safety.md) "Two Gates, One Merge-Ready Authority").

- **Once ready, stop.** When the gate proves a PR ready (safe mode) or its merge is deferred to
  a human (Pinned-Command Degradation, [reference/safety.md](reference/safety.md)), report that
  outcome and end the PR's cycle. The no-background-monitor clause (Worker Contract,
  [reference/orchestration.md](reference/orchestration.md)) governs this gate-completion step
  exactly as it governs a worker's turn. Proving readiness is never a license to arm a watch.

- **Thread resolution**. `source-control-babysit-resolve-thread` is the gate, and the mode is the
  tier's: safe lists only and never resolves; worker resolves `--autonomous`, confined further to
  threads already outdated in the PRE-push snapshot
  ([reference/orchestration.md](reference/orchestration.md)), a rule that is agent discipline rather
  than machine-enforced; autopilot widens to threads it has addressed, human threads included. The
  wrapper touches only bot-authored threads unless that widening is in play, and it can never merge,
  reply, or dismiss reviews. Never treat exit code 0 alone as proof a specific thread was resolved.
  Always parse the per-thread JSON `action` field and the run summary before reporting or
  re-checking the merge gate.

- **Independent resolution**. `--independent-resolver` is a THIRD mode, parallel to `--autonomous`
  and never a relaxation of it. `isOutdated` means the referenced code moved, so on a prose or
  documentation PR a genuinely addressed finding never becomes outdated and the worker guard refuses
  forever. This mode is dispatched to a FRESH context that is not the merging worker and did not
  author the fix, that independence is what replaces `isOutdated` as the anti-self-certification
  property, and the script cannot verify it, which is why the other half is machine-checked: one
  pinned thread per call, and a disposition whose evidence the script validates against the world
  rather than against the claim. A thread carrying more than one finding is refused outright
  (`skipped-multi-finding-thread`) and escalates: one disposition cannot clear a thread whose other
  findings nothing validated. Bot-only and the security/P1 bright line still hold. Who dispatches
  this mode, the per-finding
  D7.5 ledger owed before the wrapper is called, the fresh-pin rule, and the fail-closed fallback
  for every bound it cannot cross are the single home in
  [reference/independent-resolution.md](reference/independent-resolution.md).

- **The agent** decides severity (is this security/P1?), whether a finding is genuinely addressed,
  what a label means, and every fix-vs-escalate call, never a script. Escalate a security/P1
  thread instead of resolving it, in every tier and mode and with no exception, the wrappers refuse
  a severity-flagged thread whoever asks (`safety.md`, "Security/P1 escalation has no exception").

## Fan out: one fresh worker per PR that needs one, per cycle

Engine-backed runs (Python present) process the queue as one bounded cycle: snapshot the queue,
handle orchestrator-only transitions (stale-branch refresh, review triggers) and global cleanup
while holding the queue lease, then spawn one fresh, unbiased sub-agent per actionable PR up to
the concurrency cap. "Actionable" is every open in-scope PR the snapshot returns, narrowed only
by the deterministic exclusions, lease contention, the owner allowlist,
`mutation_policy.branch_write_allowed`, and the snapshot's `needs_worker` delta gate, never by
the orchestrator's own priority judgment. Full mechanics, the worker contract, and the prompt
template (untrusted PR fields fenced as data) are in
[reference/orchestration.md](reference/orchestration.md).

A PR that is merely unchanged since the last cycle, even one still reporting blockers it was
already escalated for, does not get a fresh worker. A non-draft PR with zero blockers **and no
untriaged material feedback** also gets no worker, only a direct mode-appropriate
`source-control-babysit-merge` gate check; that is coverage, not a skip, a PR still carrying
untriaged material findings defers to the snapshot's `needs_worker` signal instead. In default
(safe) mode, run the gate without `--merge` and report readiness without merging. Pass
`--merge --expected-head <snapshotted-head-sha>` only in `worker` or `autopilot` mode, or under
an explicit user order to merge that PR, but an enabled autopilot merge tier adds the tier flags
([reference/safety.md](reference/safety.md)), never the flagless base command. Use the exact head
SHA from the snapshot; a missing or stale pin must refuse the merge and send the PR back through
snapshot and assessment, never an unattended unpinned override.

**Zero-blocker drafts are the exception:** always route them through a worker, never directly
to the merge gate. In autopilot, that worker assesses whether the draft is complete: a
completed draft is marked ready with `gh pr ready` and continues through the normal guarded
path; a genuinely in-progress draft stays draft and is reported and escalated with the reason.
GitHub's
[draft-stage contract](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request)
confirms a draft cannot merge until it is marked ready. Completeness of the diff is not the
only hold reason: an explicit unchecked human-only item named in the PR's own body holds the
draft too, even when the content is finished and green. See the worker contract in
[reference/orchestration.md](reference/orchestration.md).

Each per-PR worker owns its local lifecycle end to end: acquire that PR's worker lease and its
own isolated worktree (find or create, never a shared checkout), check out and freshen the PR
branch, make only clear branch-owned fixes, re-check the head SHA, push, clean up on merge
(worktree + local branch), and release the lease, except a conflict worker, whose push the
orchestrator performs ([reference/orchestration.md](reference/orchestration.md)). Worktree policy:
[reference/worktrees.md](reference/worktrees.md).

## Effective configuration (substituted at load)

The values below substitute from this plugin's stored configuration when this skill loads.
A surviving literal `${user_config.…}` placeholder means that key is unset. Apply its
documented unset behavior. Reference files use `<angle-bracket>` slots; fill every slot from
this block. Values reach scripts ONLY as explicit CLI flags (option environment variables never reach skill-invoked scripts). Configuration selects targets and thresholds; it never widens tier authority.

| Key | Value | Flag delivery | Unset behavior |
| --- | --- | --- | --- |
| `babysit_watched_owners` | `${user_config.babysit_watched_owners}` | `--owners` (snapshot), `--allowed-owners` (both wrappers, fail-closed) | infer the current repo's owner |
| `babysit_self_logins` | `${user_config.babysit_self_logins}` | `--extra-self` (readiness gate and snapshot); `--self-logins` (merge gate, resolve-thread) | none. Always added to your `gh api user --jq .login` login |
| `babysit_intended_write_identity` | `${user_config.babysit_intended_write_identity}` | `--intended-write-identity` (snapshot) | attribution-drift check dormant |
| `babysit_default_tier` | `${user_config.babysit_default_tier}` | prose only. Tier of explicit bare invocations | `safe` |
| `babysit_merge_method` | `${user_config.babysit_merge_method}` | `--method` (merge wrapper) | repo convention, then squash |
| `babysit_autopilot_merge_tier` | `${user_config.babysit_autopilot_merge_tier}` | prose only. Gates whether the tier's `--autopilot-merge-tier` merge flags are wired at all | `false` (tier disabled; PRs go to the human merge-ready list) |
| `babysit_lane_logins` | `${user_config.babysit_lane_logins}` | `--lane-logins` (merge wrapper, autopilot merge tier) | tier refuses fail-closed when enabled |
| `babysit_approver_bot_logins` | `${user_config.babysit_approver_bot_logins}` | `--approver-bot-logins` (merge wrapper, autopilot merge tier) | tier refuses fail-closed when enabled |
| `babysit_merge_block_labels` | `${user_config.babysit_merge_block_labels}` | `--block-labels` (merge wrapper, autopilot merge tier) | tier refuses fail-closed when enabled |
| `babysit_review_trigger_phrase` | `${user_config.babysit_review_trigger_phrase}` | `--trigger-phrase` (snapshot, request_review) | review-trigger module dormant |
| `babysit_review_bot_logins` | `${user_config.babysit_review_bot_logins}` | `--review-bot-logins` (snapshot, request_review, merge gate) | review-trigger module dormant; merge gate's review-settle hold dormant |
| `babysit_review_gate_context` | `${user_config.babysit_review_gate_context}` | `--review-gate-context` (snapshot) | gate treated as absent |
| `babysit_review_settle_minutes` | `${user_config.babysit_review_settle_minutes}` | `--review-settle-minutes` (merge gate) | review-settle hold dormant. Pair it with `babysit_review_bot_logins`, which the gate requires alongside it |
| `babysit_ci_gateway_context` | `${user_config.babysit_ci_gateway_context}` | `--ci-gateway-context` (snapshot) | gateway check unused |
| `babysit_extra_bot_logins` | `${user_config.babysit_extra_bot_logins}` | `--extra-bot-logins` (snapshot, resolve-thread, request_review) | structural bot detection only |
| `babysit_extra_dependency_manager_logins` | `${user_config.babysit_extra_dependency_manager_logins}` | `--extra-dependency-manager-logins` (merge gate) | built-in dependabot/renovate dependency-manager set only |
| `babysit_approval_downgrade_logins` | `${user_config.babysit_approval_downgrade_logins}` | `--approval-downgrade-logins` (snapshot) | an approval carrying blocking-looking prose is downgraded to ignored structurally (every bot); a named login instead surfaces its own as material. Real APPROVED-state reviews and plain clean approvals are ignored regardless. |
| `babysit_skip_downgrade_logins` | `${user_config.babysit_skip_downgrade_logins}` | `--skip-downgrade-logins` (snapshot) | downgrade heuristic dormant |
| `babysit_max_quiet_recheck_seconds` | `${user_config.babysit_max_quiet_recheck_seconds}` | `--max-quiet-recheck-seconds` (snapshot) | `14400` |
| `babysit_stuck_check_age_seconds` | `${user_config.babysit_stuck_check_age_seconds}` | `--stuck-check-age-seconds` (snapshot) | `1800` |
| `babysit_advisory_fix_round_cap` | `${user_config.babysit_advisory_fix_round_cap}` | `--fix-round-cap` (snapshot, ledger) | `100` |
| `babysit_worker_concurrency_cap` | `${user_config.babysit_worker_concurrency_cap}` | prose only. Fan-out bound | `10` |
| `babysit_worktree_root` | `${user_config.babysit_worktree_root}` | `--root` (prune; worktree creation) | `${CLAUDE_PLUGIN_DATA}/worktrees` |
| state dir (not configurable) | `${CLAUDE_PLUGIN_DATA}/state/babysit-prs` | `--state-dir` (every state-touching script) | n/a |

Configure via the `/plugin` dialog, or headless at install time with `claude plugin install
--config KEY=VALUE`; `/source-control:setup` documents both plus the environment probes.

## Engine and degrade

The snapshot engine and gates are Python (stdlib-only) under
`${CLAUDE_PLUGIN_ROOT}/skills/babysit-prs/scripts/`; every script ships `--help`. Python is a
declared prerequisite for `worker` and `autopilot` (and for engine-backed safe runs): when it
is absent, `worker`/`autopilot` STOP at entry with a concise remediation message naming the
prerequisite, and the safe tier degrades gracefully to the Python-free loop in
[reference/loop.md](reference/loop.md). Discovery via `gh pr list`, finding classification via the
plugin-scope gate script, cadence via the static ladder. The merge gate is itself Python, so that
path cannot assess merge-readiness at all: report it unchecked, never inferred from the
classification gate. Let a safe iteration proceed when the engine is absent, reporting
merge-readiness as unchecked.

**Sandboxed sessions: the engine reads over REST, and thread resolution fails closed.**
`gh pr view --json` is implemented entirely over GraphQL, so it fails with `HTTP 403` wherever only
a pinned set of GraphQL operations is served (Claude Code on the web and remote execution), the same
restriction [`pull-request/reference/create.md`](../pull-request/reference/create.md) §2.4.0 and the
[work-item-tracker GitHub adapter](../../../work-items/tools/work-item-tracker/adapters/github/README.md)
work around. That 403 reads like an expired token or a missing scope and is neither, so take it as a
signal to switch APIs rather than to re-authenticate. The engine re-sources the whole `gh pr view`
bundle over REST by itself (`GET …/pulls/{n}` for the pull request, plus the commit check-runs and
combined-status endpoints for the check rollup), so discovery, classification, and the branch-rule
and freshness checks keep working unchanged.

Review-thread **resolution** is the one fact with no REST equivalent, and it is not approximated.
The merge gate reports `threadResolutionProven: false` and `unresolvedThreadCount: null`, never `0`,
and holds the PR with a blocker naming the restriction: an empty thread list would read as "zero
unresolved threads", the false-clean signal [reference/safety.md](reference/safety.md) exists to
prevent. Readiness there is UNPROVEN, never clean. Report it that way, quoting the blocker, and run
the lane from a session that is served GraphQL when a PR actually needs to merge. `reviewDecision`
degrades the same way: REST can prove `CHANGES_REQUESTED` but cannot prove an approval (GitHub folds
CODEOWNERS and the required-reviewer count into that field), so a protected base holds rather than
merging on evidence that does not reach it.

## Per-PR checklist (safe core, each PR, every iteration)

Execute for EACH PR discovered, oldest first. Detailed mechanics: [reference/loop.md](reference/loop.md).

- [ ] **Step 0, PR discovery:** open PRs in scope (tier-scoped author filter), oldest-first
  FIFO (§5.0.2). Zero PRs → report and schedule the idle wake
- [ ] **Step 0.1, Evidence-based fresh rescan:** fetch ALL comments via the bundled
  `${CLAUDE_PLUGIN_ROOT}/scripts/fetch-all-pr-comments.sh` (derives owner/repo from the current directory; from a cwd that is not a checkout of the target repo, export `FETCH_COMMENTS_OWNER`/`FETCH_COMMENTS_REPO` first, also unblocks the readiness gate's exit 4), filter own prior replies, classify
  addressed/unaddressed from GitHub evidence (§5.0.3). GitHub is the source of truth, not model
  memory
- [ ] **Step 0.2, Branch checkout:** put this worktree's HEAD at the true PR head (`gh pr view --json headRefOid`). `gh pr checkout <N>`, or `--detach` when the branch is locked in a sibling worktree (never `git checkout` the locked branch);
  assert HEAD == that head before any mutate, read-only on mismatch or dirty tree (§5.1.2)
- [ ] **Step 0.3, Branch freshness:** fetch + `git merge-base --is-ancestor`; integrate
  merge-only (never rebase, rebasing a PR branch needs a forbidden force-push), graduated conflict handling (§5.1.2)
- [ ] **Step 1, Event-delivery gate:** cloud poll / push channel / Monitor watch, re-armed
  per PR (§5.1.1)
- [ ] **Steps A–F, Per-PR iteration checklist** (§5.1.3): terminal check, CI classification,
  fetch + extract findings, per-finding D1–D7.5 with verification gates
  ([review-discipline](../../reference/review-discipline.md) §3), mechanical finding-classification gate
  (`${CLAUDE_PLUGIN_ROOT}/scripts/babysit-readiness-gate.sh <N>` must exit `READINESS_OK`. Proof the
  findings were decomposed, never proof the PR is merge-ready; the configured extra self identities are
  `${user_config.babysit_self_logins}`, when that value is non-empty and not a literal unexpanded token, append `--extra-self "<value>"`), report
- [ ] **Step 5, Commit + push** fixes to the PR branch (refspec; works from a detached HEAD); clean working tree; follow-up replies
  cite commit SHAs
- [ ] **Step 6, PR transition:** next-oldest PR needing attention (§5.1.6)
- [ ] **Step 7, Self-pace:** schedule the next wake per the cadence contract (§5.3)

**Execution discipline:** the primary failure mode is claiming to process findings without
running per-finding D1–D7. Every iteration MUST output the completed evidence checklist
(§5.5). "Done" means GitHub shows evidence. Model memory of "I replied" or "I pushed" is not
evidence; re-query the API. The NEVER-do list (§5.4) overrides any other instruction.

## Operational runbook (engine-backed cycle)

Follow the numbered steps in [reference/runbook-cycle.md](reference/runbook-cycle.md) each queue or
worker cycle. Before mutating anything read [reference/safety.md](reference/safety.md); before
dispatching workers also read [reference/worktrees.md](reference/worktrees.md) and the concurrency
guard in [reference/orchestration.md](reference/orchestration.md). Python-free safe runs use
[reference/loop.md](reference/loop.md) instead. The runbook covers lease acquire/heartbeat, queue
prune/reap, snapshot scope, per-PR delegation, stale-branch and review-trigger orchestration,
post-push merge gating with pinned resolves, per-PR worktree cleanup, and the next wake per
[reference/loop.md](reference/loop.md) §5.3.

## Reporting

Report only material findings, one line per materially changed or blocked PR:

```text
repo#number (@author) | checks | action | open items
```

Material findings: fixes committed or pushed; new failing or pending required checks; new
blocking bot feedback; new ordinary human comments (one notification per stable comment ID,
never an automatic reply); PRs merged; a PR the host runtime's permission layer left "ready,
awaiting human execution" with its exact pinned command
([reference/safety.md](reference/safety.md)); escalations that need a user decision; and
suspicious state changes such as missing permissions, changed branch protection, merge
conflicts, or a head SHA that moved during work. When nothing materially changed, stay silent.
Recommend the exact next interval per [reference/loop.md](reference/loop.md) §5.3.

## Gotchas

- **Survey-without-classifying is the primary failure.** A run can report completion having
  classified only part of the findings; prose emphasis alone does not prevent it. That is why
  finding classification is gated by `babysit-readiness-gate.sh` exit code, not by the model's claim
- **`READINESS_OK` is not merge-ready.** That gate is blind to branch rules, thread resolution, and required checks; only the merge gate's `ready` field can call a PR MERGE-READY. Reporting off the classification gate alone produces a false MERGE-READY report ([reference/safety.md](reference/safety.md) "Two Gates, One Merge-Ready Authority")
- **Multi-finding comments glossed as one work item.** A single comment carrying N severity
  markers is N work items; three or more findings require the extractor-subagent dispatch
  ([review-discipline](../../reference/review-discipline.md) §2)
- **Model memory across compaction is not state.** "I already replied/pushed" without an API
  re-query is a false completion claim. GitHub is the state store
- **Exploring the wrong branch produces wrong classifications.** Findings validated off the PR
  branch are confidently wrong. Checkout is mandatory before D2
- **Own prior replies re-processed as findings.** Classification-table replies from your own
  posting identities must be filtered during rescan or the loop chases its own tail
  ([review-discipline](../../reference/review-discipline.md) §1)
- **Exit codes are not per-thread outcomes.** Both wrappers demand JSON `action`-field parsing;
  a zero exit covers skipped and refused threads too
- **Self-blocking CI check.** A newly required check whose own fix PR carries that same check
  cannot be gate-merged, the check is failing or absent on the very PR that would make it pass,
  so the merge gate correctly refuses. Breaking the cycle is a one-time human admin-merge
  bootstrap of that fix PR; no tier automates it. Surface it as a blocker needing that bootstrap,
  never as a reason to route around the gate

## Reference index. Load on demand

| File | Load when |
|---|---|
| [reference/safety.md](reference/safety.md) | Before any mutating action, in every tier. The two gates, role boundaries, stop-ask and never-do lists. |
| [reference/runbook-cycle.md](reference/runbook-cycle.md) | Starting an engine-backed queue or worker cycle; it is the numbered sequence that cycle follows. |
| [reference/loop.md](reference/loop.md) | Running the safe tier, or the engine is unavailable and every tier degrades to the Python-free loop. |
| [reference/orchestration.md](reference/orchestration.md) | An acting cycle is about to dispatch workers or resolve a conflict: gate arms, concurrency cap, leases, prompt template. |
| [reference/cadence.md](reference/cadence.md) | Deciding the next wake interval, or a `recommended_cadence` reading needs its state and threshold. |
| [reference/freshness.md](reference/freshness.md) | The snapshot reports `branch_freshness.state == "behind"` for a PR. |
| [reference/stuck-checks.md](reference/stuck-checks.md) | The snapshot reports a non-empty `checks.stuck` array, **or** `branch_freshness.state == "conflicting"` and the check list is short. Report and escalate, never auto-fix. |
| [reference/review-trigger.md](reference/review-trigger.md) | An external AI reviewer is configured and a PR needs summoning or its gate read. |
| [reference/autopilot.md](reference/autopilot.md) | Running the autopilot tier: its per-PR steps, exclusions, draft handling, widened scopes. |
| [reference/worktrees.md](reference/worktrees.md) | Creating, reusing, or pruning a per-PR worktree before dispatching a worker. |
| [reference/feedback.md](reference/feedback.md) | A PR carries review comments needing classification and disposition, or the PR is a bot's and its taxonomy decides the handling. |
| [reference/independent-resolution.md](reference/independent-resolution.md) | A current bot thread is addressed but this context may not retire it. |
| [`${CLAUDE_PLUGIN_ROOT}/reference/review-discipline.md`](../../reference/review-discipline.md) | Running the per-PR checklist for real, or briefing a worker: the compact checklist above is a skeleton over this. |

