SuperAPI AI Directory

babysit-prs

General & other

View repository

Updated 10-16 00:23

About this skill

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. They moved out of pre-compute in #1619 — the harness composes the block into one shell invocation and a worktree-isolated agent refuses a git-bearing compound command — so run them individually and do not fold them back.

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. 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):

/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) no — report (simple mechanical conflicts met while freshening a branch are still handled inline per 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); 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); 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.

Autopilot merge tier (#476)

A config-gated escalation of autopilot's merge authority, shipped DISABLED and active only while the operator sets babysit_autopilot_merge_tier (enabling it, and the later gate-off flip, are separate announced steps; without it every merge decision is exactly today's). 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. That gate merges only when every criterion holds — the criteria and the safety-contract rationale are codified in 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> … spelled in the bullets below is that wrapper launched by its bin/-path form; the exact form is the single home in reference/safety.md. Both fail closed without --allowed-owners.

  • Merge readiness — source-control-babysit-merge owner/repo#N --allowed-owners <watched-owners> --self-logins @me,<self-logins> (read-only; add --merge --expected-head <vetted-head-sha> to merge, and --method <merge-method> when configured). --self-logins exempts your own PRs from the unprotected-base hold: @me resolves to your gh login, plus any babysit_self_logins extras (drop the trailing ,<self-logins> when that value is empty). It gates on GitHub's own mergeStateStatus == CLEAN plus explicit cross-checks (branch rules, review decision, unresolved threads, check rollup keyed by check type and name, head match) and reports the exact blockers. When babysit_review_bot_logins and babysit_review_settle_minutes are both set, append --review-bot-logins "<value>" --review-settle-minutes "<value>" on every form: the gate then holds a head that reviewer has not reviewed yet until the window elapses, because CLEAN is reported throughout a re-review's latency and merging inside it merges past findings that have not landed (safety.md, §Review-Settle Hold). Supply both or neither — either alone is a usage error, never a silently inert flag. If the expected-head pin is missing or no longer matches the live head, the gate refuses the merge; re-snapshot and reassess the new head instead of using --allow-unpinned-head — the wrapper rejects that flag outright, so no unattended unpinned merge exists. The pin is carried to GitHub's server-side match-head-commit guard. It refuses a dependency-manager-authored PR absent --allow-dependency (held set: built-in dependabot/renovate plus any babysit_extra_dependency_manager_logins, which you MUST append via --extra-dependency-manager-logins "<value>" when set — see safety.md's merge command forms — or those extra bots are silently not held), refuses merge on an unprotected repo (zero required reviews and zero required contexts) for a non-self author absent --allow-unprotected, never uses --admin, and cannot resolve threads, reply, or force-push. React to blockers; do not bypass the gate. A ready:false immediately following a ready:true on the same expected head is often GitHub's own mergeability recompute lag — re-run the read-only check once before treating it as a real block. 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 "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), report that outcome and end the PR's cycle. The no-background-monitor clause (Worker Contract, 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 owner/repo#N --allowed-owners <watched-owners> --extra-bot-logins <extra-bot-logins> --self-logins @me,<self-logins> (lists by default; add --resolve). By default it touches only bot-authored threads (structural __typename == "Bot" or the [bot] login suffix — no hardcoded identity list) and never a human thread; --extra-bot-logins extends that set with the configured non-structural bot accounts (dropping it silently reclassifies their threads as human), and --self-logins rides on every form too — omitting it lets the worker's OWN bot-thread reply flip botOnly false and strand the thread outside every resolution scope (safety.md). In worker tier pass --autonomous, which resolves only threads GitHub marks isOutdated, each pinned via --expected-comment-count and --expected-last-updated. Those pins enforce comment-state only — they block a thread whose comment count or latest comment-edit timestamp drifted after vetting. The worker must additionally confine resolves to threads already outdated in the PRE-push snapshot (reference/orchestration.md); that pre-push-outdated rule is agent discipline, not machine-enforced, so a thread a worker's own push merely displaced (isOutdated flipped while both comment pins still match) is still resolvable — the machine-enforced fix for that displacement bypass is tracked in #571. In autopilot pass --resolve --include-human for threads the agent has addressed; the script still cannot 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 (resolved vs skipped-* / refused-stale-pin / resolve-failed) and the resolvedCount/eligibleCount summary before reporting or re-checking the merge gate. --resolve --thread-id without matching --expected-comment-count and --expected-last-updated (or an explicit --allow-unpinned-thread override) is refused before anything is fetched or resolved.

  • 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. Pass one --thread-id (bulk refused in every mode here, list included), both pins, and --disposition fixed|deferred|incorrect with its own evidence flag: --fix-commit <sha> must be reachable from the PR head, --tracker-item <id> must exist and be open, --counter-evidence <text> must already appear in a reply on the thread posted by someone OTHER than the thread's opener. The script validates evidence against the world, not against the claim, in list mode too — anything missing, unparsable, or unverifiable refuses with its own refused-* action rather than warning, and only a confirmed HTTP 404 is read as the world saying no. 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, and --autonomous, --include-human, and --allow-unpinned-thread are all refused alongside it.

  • 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, even in autopilot — one named exception, scoped in safety.md.

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.

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), 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 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.

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). Worktree policy: 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) —

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 — 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. Never block a safe iteration on the engine's absence.

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

Execute for EACH PR discovered, oldest first. Detailed mechanics: 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 §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)

  1. Read reference/safety.md before mutating anything; before dispatching workers also read reference/worktrees.md and the concurrency guard in reference/orchestration.md. Python-free safe runs follow reference/loop.md instead of this runbook.

  2. Acquire the deterministic lease for this run (queue scope for a full cycle, worker scope with --pr for a single PR), retain the token, and heartbeat it on a bounded cadence: python "${CLAUDE_PLUGIN_ROOT}/skills/babysit-prs/scripts/manage_babysit_lease.py" acquire --scope queue --state-dir <state-dir> (add --repo <owner/repo> when sharding sessions per repo). On exit 3 either reclaim a provably dead holder with --steal-stale or skip; treat a token-mismatch heartbeat as lost ownership (reference/orchestration.md).

  3. In queue mode only, while holding the queue lease, prune unleased clean merged/closed worktrees and reap expired worker leases: python "${CLAUDE_PLUGIN_ROOT}/skills/babysit-prs/scripts/prune_babysit_worktrees.py" --apply --root <worktree-root> --state-dir <state-dir> and python "${CLAUDE_PLUGIN_ROOT}/skills/babysit-prs/scripts/manage_babysit_lease.py" reap --apply --state-dir <state-dir>.

  4. Run the snapshot with the tier's scope: python "${CLAUDE_PLUGIN_ROOT}/skills/babysit-prs/scripts/pr_queue_snapshot.py" --queue --author @me --owners <watched-owners> --state-dir <state-dir> --write-state (the @me scopes discovery to your own gh login; when babysit_self_logins is non-empty and not a literal unexpanded token, append --extra-self <self-logins> — those extra posting identities join the self-suppression set independently of --author, surviving autopilot widening; when babysit_intended_write_identity is set and not a literal unexpanded token, append --intended-write-identity <intended-write-identity> so a wrong-self-login write surfaces as attribution drift; append review-trigger flags only when configured; --pr owner/repo#N (single PR) or --repo <owner/repo-csv> (sharded); drop --author only to widen — self-suppression no longer rides on it). Capture the prior cycle's generated_at per reference/cadence.md before writing new state.

  5. Decide per PR from the snapshot's classification, needs_worker, recommended_cadence, and material_findings: delegate a worker (only when needs_worker is true), act locally, report, back off, or escalate. Load reference/freshness.md only when a branch is behind, reference/stuck-checks.md only when a PR's checks.stuck is non-empty (escalate the routing, never auto-fix), reference/feedback.md and reference/review-trigger.md only for feedback or review gates, the fan-out gate in reference/orchestration.md only before assigning workers, and reference/cadence.md only before interpreting a cadence state.

  6. Process stale-branch refreshes and review-trigger posts as orchestrator-only actions before assigning workers; each is terminal for that PR's cycle until a later snapshot observes its new head (reference/freshness.md, reference/review-trigger.md).

  7. In worker mode, after a worker's fix is pushed and its checks are green, take a fresh post-push snapshot (or use the exact pushed commit after the worker has vetted that commit), then run the merge gate with --merge --expected-head <post-push-head-sha> only when it reports ready. Never reuse the pre-worker snapshot pin after the head moves — except a lane-pinned invocation (reference/safety.md, "Lane-pinned merge authorization"), which reports the moved head instead of re-pinning, at every merge-capable tier. Resolve pre-push-outdated bot threads that block the gate — once the agent has confirmed they are not security/P1 — as a per-thread vetted loop: one --autonomous --resolve --thread-id <id> --expected-comment-count <n> --expected-last-updated <ts> call per thread, pins taken from the same snapshot that vetted it. --autonomous --resolve refuses a bulk (no --thread-id) call, so the comment-state pins are always enforced (a reply or edit after vetting blocks the resolve). Those pins do NOT catch displacement — a push that flips isOutdated while the comment count and last-updated still match is still resolved — so keeping such a thread unresolved rests on the pre-push-outdated agent-discipline rule, with the machine-enforced fix tracked in #571. In autopilot, after addressing the findings, additionally resolve AI-review and human threads with --resolve --include-human, then run the same pinned merge gate — the gate is never bypassed. After any --resolve run, parse its JSON output (per-thread action, and resolvedCount) before re-running the merge gate.

  8. After each PR is integrated, prune only that PR's clean worktree with --pr, --lease-token, and --prune-open-clean, delete its local feature branch on merge, then release its worker lease. Never globally prune open-PR worktrees. Release the queue lease in finally-style cleanup.

  9. Schedule the next wake per the cadence contract in reference/loop.md §5.3.

Reporting

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

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); 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 §5.3.

Gotchas

Failure patterns observed in real babysit sessions:

  • Survey-without-classifying is the #1 failure. An audited run classified 16 of ~32 findings while reporting completion — prose "MANDATORY" alone under-decomposes. 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 produced a false MERGE-READY report (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; ≥3 findings REQUIRE the extractor-subagent dispatch (review-discipline §2)
  • Model memory across compaction is not state. "I already replied/pushed" without an API re-query has produced false completion claims — GitHub is the state store
  • Exploring the wrong branch produces wrong classifications. Findings validated off the PR branch have been 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 §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

References

  • reference/loop.md — the safe-tier iteration loop (also the Python-free degrade path): discovery, checkout, freshness, checklist, and the §5.3 cadence contract.
  • reference/orchestration.md — fan-out gate (needs_worker arms), concurrency cap, leases, worker contract + prompt template, conflict resolution (the resolve/push split and the conflict-worker prompt delta), cleanup.
  • reference/cadence.md — active/normal/quiet/idle cadence states, real-elapsed-time detection, bounded full-sweep interval, persisted counters.
  • reference/freshness.md — guarded refresh for behind-base branches, BLOCKED compare fallback, async-update terminality.
  • reference/stuck-checks.md — the checks.stuck signal (checks holding mergeStateStatus at UNSTABLE without completing) and its escalation routing; report, never auto-fix.
  • reference/review-trigger.md — generalized AI-review trigger + gate semantics; dormant when unconfigured.
  • reference/autopilot.md — the autopilot tier's per-PR steps, its deterministic-only exclusions, draft handling, and the scopes it widens.
  • reference/worktrees.md — ephemeral worktree policy and prune commands.
  • reference/safety.md — the two gates and which one owns merge-readiness, role boundaries, verify-before-escalate, the harness permission layer (pinned-command degradation), stop-ask and never-do lists.
  • reference/feedback.md — feedback classification, dispositions, advisory cap, bot-PR taxonomy, human-feedback policy.
  • ${CLAUDE_PLUGIN_ROOT}/reference/review-discipline.md — the plugin-scope per-PR review discipline shared with /source-control:pull-request.