Files
2026-07-13 12:20:01 +08:00

14 KiB

ce-babysit-pr

Watch an open PR and keep it moving toward merge. React to CI failures and incoming review comments as each arrives — comments first — and report when it looks ready, surfacing anything that needs a human decision rather than forcing it.

ce-babysit-pr is the post-open PR watch loop. After /ce-commit-push-pr opens a PR, this skill watches its two independent event streams — incoming review comments and CI status — and reacts to whichever fires first, until the PR looks merge-ready, is blocked on a human decision, or is terminal. It is a thin conductor: it does not resolve feedback or fix CI itself. It delegates — review comments to /ce-resolve-pr-feedback, CI failures to /ce-debug — and owns only what no other skill covers: the loop, the ordering, dedup across ticks, the settle window, and the stop decision.

It cannot guarantee merge-readiness, and does not pretend to. A reviewer can always add feedback later; required checks can change. The skill drives the PR forward and tells you when it looks ready — the merge stays yours. The safety judgment for "would this fix change behavior I intended?" lives in /ce-resolve-pr-feedback (which escalates such fixes to needs-human), so the babysit loop can run autonomously without silently changing intended behavior.

The compound-engineering shipping chain is /ce-work → /ce-commit-push-pr → (reviewers comment) → /ce-resolve-pr-feedback. ce-babysit-pr sits on top of that last step, invoking it on a schedule instead of by hand, and interleaving CI fixes.


TL;DR

Question Answer
What does it do? Watches an open PR and keeps it moving toward merge, reacting to review comments and CI as they arrive
When to use it After a PR is open and you want it moved toward merge hands-off (offered automatically at the end of /ce-commit-push-pr)
What it produces Delegated fixes (feedback + CI), surfaced human-decision escalations, and a high-level summary of what got the PR to where it is
How it does work Delegates: comments → /ce-resolve-pr-feedback, CI → /ce-debug. Owns only the loop
Modes Self-sustaining in-session watch (default) or Checkpoint (one tick + resume command, where the harness has no background-and-wake capability)

The Problem

Babysitting a PR by hand — or with a naive loop — fails in predictable ways:

  • Serialized timelines — the common mistake is "wait for the whole CI run, then read comments." That burns an entire CI cycle per round. A comment fix pushes a new commit that re-triggers CI anyway, so comments should be handled while CI runs.
  • Premature "ready to merge" — CI goes green, the loop declares victory, and then review feedback lands. You go to merge and find surprises.
  • Reinventing engines — a monolithic babysitter re-implements feedback resolution and CI debugging that already exist as dedicated skills, and drifts from them.
  • Loops that don't survive the harness — an in-session sleep loop can't run in a GUI app harness that sandboxes the turn, and Claude Code blocks foreground sleep outright.
  • Opaque endings — the run stops and you're not sure what it did, or it dumps a wall of per-thread receipts you have to wade through.

The Solution

ce-babysit-pr runs as a stateless, resumable tick, driven by whatever background-and-wake capability the current harness actually has:

  • Comments-first ordering with stale-SHA cancellation — each tick handles new review threads before CI; after the comment pass it re-snapshots, and if that pass pushed a commit it discards the now-stale CI failure rather than fixing a dead SHA.
  • Delegation, not reimplementation/ce-resolve-pr-feedback for comments, /ce-debug for real CI failures (dispatched once per new failure signature, never every poll). The only inline CI logic is cheap flaky-vs-real classification to decide which skill to call.
  • A settle window — "looks ready" requires GitHub to report the PR mergeable (mergeStateStatus == CLEAN) and no open threads and the PR unchanged for a minimum elapsed quiet time, so a late reviewer resets the clock instead of being missed. It is a cooling-off signal, not a merge guarantee.
  • A self-sustaining in-session watch (the default) — a token-free background change-detector (pr-snapshot watch) wakes the agent in-session only when something actionable changes, so the loop keeps every decision the conversation made. Where the harness has no background-and-wake capability, it falls back to checkpoint (one tick + the exact resume command).
  • A high-level final summary — outcome first, grouped and counted, no receipts.

What Makes It Novel

1. Comments-before-CI, then cancel the stale SHA

The ordering invariant is the point of the skill. Within a tick: terminal check → resolve new comments → re-snapshot → only then act on CI, and only if the comment pass didn't already push (which would have re-triggered CI on a new SHA). A CI fix is never applied against a pre-comment SHA. This collapses the comment and CI timelines instead of serializing them.

2. Stateless, resumable tick — one loop, any driver

All state lives on disk (/tmp/compound-engineering/ce-babysit-pr/<owner>-<repo>-<pr>/state.json), so a tick is idempotent and any re-invocation drives it: an in-session background-and-wake wait, /loop, a durable scheduler, or the user re-running the skill an hour later. This is what makes a single authored-once skill portable across CLI and app harnesses — the loop mechanics don't depend on any one driver that may not exist.

3. A self-sustaining in-session watch, not a per-harness scheduler

A skill's turn ends when it returns, so the skill sets up its own loop — nothing re-invokes it by magic. The robust, cross-harness-verified way is not to call a specific scheduler; it is to background a cheap deterministic change-detector — pr-snapshot watch (same fetch→diff, no agent tokens, prints one BABYSIT_WAKE sentinel only on an actionable change or a stop condition) — and stay in-session, woken by that sentinel. The one capability needed is generic — run a background process and be woken when it emits a line, without ending the turn — so the skill describes the capability and uses whatever tool the harness exposes (Claude Code background Bash + a Monitor/wait, Grok get_command_or_subagent_output or scheduler_create --durable, Cursor Shell + notify_on_output, a runtime-owned background exec on Codex — a detached nohup is reaped there). Staying in-session is what preserves the conversation's decisions — declined nits, a reviewer judged wrong, mid-run steering — and spends reasoning only when something changed. Where no such capability exists, it falls back to checkpoint: one tick, persist, print the resume command, say plainly monitoring is paused — the same loop, hand-cranked. For an unattended multi-day watch, escalate to a durable scheduler (Grok scheduler_create --durable, or cron running <cli> exec "/ce-babysit-pr <url>"), accepting that a fresh headless run reconstructs from disk and is context-blind.

4. The settle window beats bot-signal parsing

Instead of maintaining a brittle per-bot matrix of "is this reviewer mid-review" (👀 reactions, "reviewing…" comments — and many bots leave no trace), the skill waits for elapsed quiet time. Any movement — a check, a thread edit, a new head, a review-decision change, a mergeability change — resets quiet_seconds. A bot mid-review is recent activity, so it's caught for free. An in-progress emoji, if noticed, only ever extends the wait; it's never required. The window is a cooling-off signal, not a guarantee — evidence the PR stopped moving, not proof no review is coming — so the skill reports "looks ready, your call," never "safe to merge." Merge-readiness itself defers to GitHub's own mergeStateStatus == CLEAN rather than re-deriving which checks are required.

5. Claim → act → confirm (crash-safe dedup)

The snapshot never marks an item handled just from observing it. An item leaves the actionable set only when the agent confirms it acted (a mark after a resolve/debug pass) or when remote truth removes it (a resolved thread drops out of the unresolved fetch). So a resolve pass that crashes, errors, or returns without finishing leaves its items actionable on the next tick — the loop cannot silently drop work. A failing check stays actionable until marked dispatched at the current head; a new head SHA clears that, re-evaluating every check against the new commit. New activity on an escalated thread (an edited or added comment) reactivates it automatically.

6. A trustworthy ending

Every stop and every checkpoint tick ends with an outcome-first summary — looks-ready / blocked / paused, then grouped-and-counted work (threads resolved across N rounds, CI failures fixed), then the specific blocker or the resume command. No per-thread receipts. Crucially, it surfaces the judgment calls — where the loop did something other than the literal ask (a fix done differently than suggested, feedback declined or rebutted, an escalation, or a call a human steered) — with a one-line why, while routine "reviewer asked, we fixed it" changes stay in the aggregate count. You see the decisions made on your behalf, not a transcript of every edit.


When to Reach For It

Reach for ce-babysit-pr when:

  • A PR is open and you want it driven toward merge without hand-holding each round
  • You're about to context-switch away but want CI failures and review comments handled as they come in
  • /ce-commit-push-pr just opened a PR and offered the babysit handoff

Skip it when:

  • The repo is not on GitHub — the skill is GitHub-only (it and its delegates use gh, review threads, and Actions). It detects a non-GitHub remote (GitLab, Bitbucket) up front and stops rather than half-running.
  • No PR exists yet, or the PR is already merged/closed
  • You want to review and approve each fix yourself before it's pushed — use /ce-resolve-pr-feedback directly, one pass at a time
  • The only issue is a single known bug to fix — use /ce-debug

Platform support. GitHub only today. GitLab is mappable in principle — glab, merge requests, MR discussions (resolvable/resolved), pipelines, and detailed_merge_status are clean equivalents — but it would require glab-based variants in both pr-snapshot's fetch layer and ce-resolve-pr-feedback's resolve scripts, and hasn't been built or tested. The platform-specific seam is pr-snapshot's fetch/fetch_threads; a future GitLab path would swap those behind a platform flag.


Use as Part of the Workflow

/ce-work → /ce-commit-push-pr → /ce-babysit-pr
                                     ├── new review comments → /ce-resolve-pr-feedback
                                     └── real CI failure      → /ce-debug

It complements:

  • /ce-resolve-pr-feedback — the engine ce-babysit-pr calls for each round of comments; run it directly when you want a single manual pass
  • /ce-debug — the engine ce-babysit-pr calls for genuine CI failures
  • /ce-commit-push-pr — opens the PR and offers the babysit handoff

Use Standalone

  • Current branch's PR/ce-babysit-pr
  • Specific PR/ce-babysit-pr 1234 or /ce-babysit-pr <PR-url>
  • Force a mode/ce-babysit-pr 1234 checkpoint (or watch)

Reference

Argument Effect
(empty) Current branch's PR, mode inferred from harness capability
<PR number or URL> That PR
watch / checkpoint Force the execution mode

scripts/pr-snapshot is the deterministic snapshot + state helper: it fetches both event streams, reads/writes state atomically under a lock, and emits the per-tick actionable set with quiet_seconds for the settle window. Its watch subcommand is the token-free change-detector that backs the in-session loop — it polls the same fetch→diff and prints a single BABYSIT_WAKE sentinel only when there's an actionable change or a stop condition. references/watch-loop.md documents how the watch sustains itself, the state schema, dedup identities, settle window, and edge cases.


FAQ

Does it merge the PR for me? No. It keeps the PR moving and tells you when it looks ready — once GitHub reports it mergeable and the PR has been quiet for the settle window; the merge itself stays yours. It cannot guarantee no further feedback is coming.

Why not just wait for CI, then handle comments? Because a comment fix pushes a commit that re-triggers CI anyway. Handling comments while CI runs collapses the two timelines; waiting serializes them and wastes a full CI cycle per round.

How does it avoid the "green, then surprise feedback" trap? It never calls a PR ready on a single green snapshot. It requires the PR to be unchanged for a settle window (default 5 min of elapsed quiet time) and GitHub to report it mergeable. Late activity resets the clock. Even then it says "looks ready, your call" — the window is a cooling-off signal, not a promise no review is coming.

Does it run forever in the background? By default it runs a self-sustaining in-session watch: a token-free background change-detector (pr-snapshot watch) wakes it only when something actionable changes, so it keeps watching without burning reasoning on quiet polls — but it's session-bound (re-invoking resumes cleanly from disk). Where the harness has no background-and-wake capability, it falls back to checkpoint — one tick, then the resume command — and for an unattended multi-day watch you escalate to a durable scheduler (e.g. Grok scheduler_create --durable, or cron). It never fakes a loop with blocked/foreground sleep or a reaped nohup.

Does it fix CI failures itself? It classifies cheaply (flaky → one rerun; real failure → /ce-debug) but delegates the actual diagnosis and fix to /ce-debug, and comment fixes to /ce-resolve-pr-feedback. It doesn't reimplement either.

What about merge conflicts? It stops and reports the conflicted files. It does not auto-rebase or force-push a PR head branch — that's destructive and out of scope for a watcher.


See Also