Imported from STEP-Network/dev-tasks (
plugin/skills/babysit-prs/SKILL.md). Install upstream withnpx skills add STEP-Network/dev-tasks --skill babysit-prs. Copyright stays with the author.
/babysit-prs — Orchestrator PR Merge Loop
Read .claude/project-config.json. Extract git.defaultBase (PRs polled here) and git.hotfixBase (PRs here require human merge).
Subagents can't poll CI — they're one-shot. After a subagent runs /ship-pr and hands off, someone must handle the merge. That's this skill, run by the main session.
Three use cases per .claude/rules/agent-autonomy.md:
- Subagent-produced PRs (most common): Task() subagent →
/ship-pr→ SendMessage handoff → main session runs/babysit-prs. - Multi-agent fan-out: N parallel subagents, each producing a PR. One polling loop.
- Recovery from prior session: previous run ended before push reached merge state.
Single-agent main-session work uses /ship-pr Phase 6.6 autonomous merge — no /babysit-prs needed.
When NOT to invoke
- Single-agent main-session work (use Phase 6.6 instead)
- Hotfix PRs targeting
$hotfixBase(human merge perrelease-flow.md) - About to end the session (leave open PRs for next orchestrator)
Workflow
Phase 1: Survey
gh pr list --base $defaultBase --state open --json number,title,headRefName,updatedAt,mergeStateStatus- Classify each:
state=MERGED→ skipstate=OPEN+mergeStateStatus=CLEAN→ ready to mergeOPEN+UNSTABLE→ CI/checks in flight; inspect viagh pr view {N} --json reviews,comments(often = required-check missing rather than failed)OPEN+BLOCKED→ real CI failure or missing approvalOPEN+DIRTY→ merge conflict; needs rebaseOPEN+UNKNOWN→ recheck in 30s
- Report one-line summary per PR.
Phase 1b: Wait for configured reviewers (reviewer-wait gate)
Before evaluating merge readiness, wait for ALL configured review sources to have posted. This prevents the race condition where merge fires on CI-green before the Claude bot or Corridor even had time to analyze the PR.
-
Read configured sources from
project-config.json→review.sources[](default:["claudeBot", "corridor", "selfReview"]). For each source, define the expected signal:claudeBot: a comment byauthor.login == "claude"containing"## Code Review"corridor: Corridor findings returned bygetFindings(even if empty — presence of response = posted)selfReview:selfReviewPassed: truein active-task.json (set by the producing agent)vercelAgent: a PR review byauthor.login == "vercel[bot]"or Vercel commentseer: a PR or linked PR byauthor.login == "sentry-io[bot]"
-
For each PR at CLEAN: check which configured sources have NOT yet posted.
-
If any source is missing: wait up to
review.reviewerTimeoutSeconds(default: 600s / 10 min), polling every 60s. Use the transition-only Monitor pattern (emit only on new-source-arrival). -
Timeout without all sources posting: do NOT merge optimistically. Instead:
- Log which source(s) are still silent
- File a Retro (type: Improve) noting the reviewer timeout and the PR
- Mark the PR as "reviewer-timeout — requires manual merge or re-run" in the sweep log
- Continue to the next PR in the sweep
This gate fires BEFORE Phase 2's verdict check — a positive verdict from one source doesn't override the absence of another configured source.
Phase 2: Merge sweep
For each PR that passed Phase 1b (all configured reviewers posted):
- Verify review state via
gh pr view {N} --json comments— latestclaudeauthor comment. Positive verdicts: "ship-ready", "ship as-is", "no BLOCKERs", "all checks pass", "Self-Review PASSED", "verdict: green", or 🟢. If BLOCKERs surfaced → Phase 2b. - Read PR body for Monday task ID:
gh pr view {N} --json body | grep "Monday\.com Task". - Ensure
reviewAddressedis populated in the PR's worktree active-task.json (structured format preferred — see/ship-prSKILL.md Phase 6 schema). If the producing agent used"handoff-to-orchestrator", the orchestrator must now perform its own triage pass and write the structuredreviewAddressedbefore merging. Thepre-merge-review-gatehook enforces this. - Merge:
gh pr merge {N} --admin --squash. NEVER--delete-branch—ghtries to delete the local tracking branch by switching cwd's checkout to$defaultBase, which fails withfatal: '$defaultBase' is already used by worktreeand can corrupt the active task's branch state in a worktree session. - Local cleanup:
git fetch --prune origin. Drops the stale ref. Safe from any worktree. - Capture merge SHA:
gh pr view {N} --json mergeCommit --jq .mergeCommit.oid.
Phase 2b: Handling BLOCKER findings
Triage per finding:
| Finding | Action |
|---|---|
| <10 lines, low-risk, single-file | Inline fix from orchestrator in .claude/worktrees/feat-<slug>/ |
| Multi-file, requires tests, or > ~30min | Spawn fixup subagent via Agent({ run_in_background: true }) with surgical brief |
| Genuinely incorrect Claude verdict | Decline via gh pr comment {N} --body ... |
| Architectural — needs human input | TASK_STUCK + SendMessage user OR file retro |
Inline-fix flow: cd to worktree → edit → quick sanity check → git add . && git commit -m "fix: address round-N BLOCKER — <summary>" → git push → re-arm Phase 6 Monitor for round N+1.
Fixup subagent: write a surgical prompt naming the worktree path, branch, PR, the BLOCKER excerpt, the fix directive, and standard rules (no /pickup-task, no merge, no full /ship-pr cycle — just fix + push + report). Spawn with Agent({ subagent_type: 'general-purpose', run_in_background: true }). Estimate 5–15 min.
After fixup pushes, re-arm Phase 6 Monitor for round N+1. Loop terminates when round-K is READY.
Phase 3: Monday reconciliation
For each freshly-merged PR (if MCP up):
getTask({ itemId: taskId, format: "json" })— read state.- If
sprintslacks active sprint:updateTask({ itemId, sprintId: <active> })(vialistSprints({ activeOnly: true })). - Set missing links:
updateTask({ itemId, prLink, branch, githubLink, demoUrl }). - If parent status not
Waiting for UAT:- Missing UAT doc →
createTaskUatDoc({ taskId, markdown })from PR body + AC - Verify the staging deploy is
READYFIRST. Flipping the last subtask Done auto-flips the parent toWaiting for UAT, and that status is a lie until the post-merge deploy is live (PR #347 / retro #2926719311). Pollmcp__vercel__list_deploymentsfiltered bymeta.githubCommitSha = <merge SHA from Phase 2 step 6>until a production-target deployment reachesREADY(non-Vercel:gh run watch/flyctl status/ your platform's check). Under CI (GITHUB_ACTIONS/CI) a runner can't reach Vercel — skip the poll. Do this BEFORE flipping the final subtask. - Flip subtasks Done via
manageSubtasks({ parentItemId, operations })withactualHours— flip the LAST one only after the deploy isREADY. - Monday automation auto-flips parent when all subtasks Done
- Full-suite E2E advisory run (v0.34.0) — once the staging deploy is
READY(above), invoke/dev-tasks:run-full-e2e --taskId=<taskId>to run the consumer's full Playwright suite in-session against staging and record the result (this is the orchestrator-side analog of/ship-prstep 20f.8, including any after passvisualDiff.afterPendingdeferred to this loop). ADVISORY — it never gates; the skill's own safe-skip makes it a recorded no-op for projects without a staging URL + a real suite, and it never errors. Fold thefullSuiteE2Eresult into the per-PR summary below.
- Missing UAT doc →
- Post the single per-PR final summary via
createUpdate({ itemId, body: HTML })(the orchestrator-side analog of/ship-pr's[PIPELINE_COMPLETE]— one summary per merged PR, not a per-step narrative stream). Skip per-step narrative event posts (noCI_PASSED/REVIEW_ACCEPTEDUpdates) — progress is tracked in git commits (every commit carries the task#id). - Record the merge SHA as reconciled (required when
stop-monday-reconciled-checkis enabled in the orchestrator session — else the hook fires at orchestrator session-end because merges landed without their SHAs appearing in.claude/active-task.jsonmondayReconciledShas[]). Emit the marker first, then append:
Then append the SHA captured in Phase 2 step 6 to the orchestrator's ownbash ${CLAUDE_PLUGIN_ROOT}/scripts/emit-state-marker.sh mondayReconciledShas.claude/active-task.jsonmondayReconciledShas[](initialize as[]if absent). The marker unlocksprotect-active-task-statefor this append. Subagent-driven worktree sessions don't need this — they're already removed in Phase 4.
MCP down: capture state inline as checklist; defer reconciliation; catch up immediately on recovery.
Phase 4: Worktree cleanup (mandatory, not optional)
For each merged PR, run the canonical wrapper:
bash ${CLAUDE_PLUGIN_ROOT}/scripts/post-merge-cleanup.sh <PR>
Wrapper does:
- Resolves worktree path from PR's branch (
branch.replace('/', '-')→.claude/worktrees/<slug>). git worktree remove --force <path>(--force because remote branch deleted by merge).- Prints a checklist of remaining manual steps (createUpdate with merge SHA, verify demoUrl, manageSubtasks Done, SendMessage shutdown — Phase 5).
Resolves the consumer project root from $CLAUDE_PROJECT_DIR (Claude Code sets it at session start) so it works invoked from the plugin path.
Batch mode for retroactive cleanup of multiple DONE worktrees: bash ${CLAUDE_PLUGIN_ROOT}/scripts/worktree-audit.sh --remove -y.
The full post-merge contract — what the orchestrator owes the Monday board for each merged PR (createUpdate, manageSubtasks, worktree-remove, SendMessage, demoUrl verify) — lives in agent-orchestration.md "Orchestrator post-merge checklist". This Phase 4 is the mechanical wrapper; the rule is the spec.
Phase 5: Agent shutdown
For each subagent whose PR merged: SendMessage({ to: <agent>, message: { type: "shutdown_request", reason: "PR #{N} merged at {time}. Work complete." } }).
Phase 6: Continue or exit
More PRs/subagents pending → stay. Queue empty + no agents working → end session.
Polling pattern
Use a Monitor that evaluates current state on first iteration (so an already-posted verdict is matched immediately) and continues watching for new comments. Match either the ship-ready predicates (READY → orchestrator merges) or BLOCKER predicates (BLOCKER → fix inline or send back). Also break on state=MERGED. Set last_seen="" on init — seeding it with the current latest makes the Monitor watch for a SECOND comment that may never come.
A wait condition that ignores current state and only watches future events is broken — always evaluate current state on first iteration. Don't wait for mergeStateStatus=CLEAN — it can be permanently UNSTABLE from secondary workflow noise.
See plugin/rules/monitor-predicate-pattern.md for the two patterns that govern Monitor emission cadence (transition-only) + post-success action timing (act in the same response — don't narrate between Monitor returning and the merge call).
Anti-patterns
- DO NOT use
gh pr merge --auto— flaky against this repo's CI (UNSTABLE noise blocks auto-merge). Use--admin --squash. - DO NOT merge PRs targeting
main— hotfix PRs require human merge. - DO NOT delete the worktree before merging — git refuses (branch checked out).
- DO NOT skip Monday reconciliation if MCP up — post-merge state is the audit trail.
- DO NOT batch reconciliation across MCP outage windows — catch up immediately on recovery.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
gh pr merge "branch is checked out" |
Worktree still has the branch | Force-remove worktree, then merge with admin |
| "PR already merged" | Status update lag from prior tick | Verify via gh pr view --json state; skip to reconciliation if MERGED |
updateTask 404 on subtask IDs |
Subtasks on different board | Use manageSubtasks({ parentItemId, operations }) |
| Monday MCP "Server not found" | OAuth session switched | Defer; capture queue inline; retry on MCP recovery |
CLEAN but --admin fails |
Branch protection requires a check the agent didn't see | Check gh pr checks {N} for blocker; add via PR body edit |
Output
Keep status lines short:
Sweeping PR queue (N open targeting staging):
#283 feat/foo — CLEAN → merging
#285 feat/bar — UNSTABLE (claude-review pending) → skipping
#286 feat/baz — DIRTY (conflict) → escalating
Merged: #283 at {SHA}, Monday #2915513137 → Waiting for UAT
Next sweep: 5min, or when agent notifies
Dispatching subagents
When the orchestrator spawns a fix-class subagent (the most common /babysit-prs precursor), use the canonical dispatch template:
cat ${CLAUDE_PLUGIN_ROOT}/templates/agent-dispatch-fix.md
Fill the placeholders (<task-ID>, <task-name>, <branch>, etc.) — keep the Anti-shortcuts + Hard-rules sections intact. The template enumerates the workflow-enforcement hooks the subagent will encounter so it doesn't discover them mid-implementation.
Cross-references
/ship-prPhase 6.6 — main-session autonomous merge.claude/rules/release-flow.md— branching + merge policy.claude/rules/task-lifecycle.md— status transitions.claude/scripts/worktree-audit.sh— batch worktree cleanup