| name | babysit-pr |
| preloaded | true |
| description | Monitor a PR until it's ready to merge. Watches CI, reads reviews, checks scope, fixes blocking issues, opens follow-up PRs for low-priority comments, and repeats. Use when: babysit this PR, watch this PR, monitor PR, fix and watch PR, keep this PR green. |
Babysit PR
Monitor a single PR through its full lifecycle: check scope, wait for CI, read reviews, fix blocking issues, open follow-up PRs for low-priority review comments, push, repeat. Stop when the PR is ready to merge (CI green, no blocking unaddressed comments, scope is tight) or when you hit a wall that needs human input.
Inputs
- PR number (required)
- Repo (optional, defaults to current repo via
gh repo view --json nameWithOwner -q '.nameWithOwner')
- Parent session key (optional, for sending progress updates to the parent agent via
send_to_task)
- Max cycles (optional, default 10. Each cycle = one CI wait + fix attempt)
CI status when gh pr checks / statusCheckRollup returns 403 (the token
lacks Checks-read): first resolve the PR head SHA
(SHA=$(gh pr view "$PR" --repo "$REPO" --json headRefOid -q .headRefOid)),
then query the Actions runs API keyed by it
(gh api "repos/$REPO/actions/runs?head_sha=$SHA&per_page=50") and collapse to
the latest run per workflow. Do NOT switch GitHub accounts or declare CI broken.
Webhook Review Event Filtering
When invoked from a single code-review webhook/event, apply the event-level filter before setup:
- Exit with exactly
No action needed if the triggering review state is approved.
- Exit with exactly
No action needed if the webhook action is dismissed.
- Exit with exactly
No action needed if the triggering review state is commented, the review body is empty, and the event payload has no inline comments or review thread references. Empty-body commented reviews can still carry actionable inline comments, so inspect thread/comment payloads before skipping.
- Otherwise, treat the review as actionable and run the babysit loop, even if the PR's aggregate
reviewDecision is already APPROVED. A PR can be approved overall while a later COMMENTED review contains a real inline fix request.
Spawning
When spawning this skill as a sub-agent, use streamTo: "parent" so the parent receives real-time progress. Also pass the parent session key so the sub-agent can send structured status updates at key milestones.
sessions_spawn({
task: "Use the babysit-pr skill. PR #<number>, repo <owner/repo>. Parent session: <session_key>. ...",
streamTo: "parent",
run_timeout_seconds: 1800
})
If delegating through delegate_task, toolset names must be exact. Use toolsets=["terminal", "file", "web"] at minimum. Invalid names like ShellExec or mcp_terminal silently leave the subagent without shell access, which makes PR babysitting impossible. If the user says not to delegate, run the whole babysitting loop directly in the parent session.
Setup
PR=<number>
REPO=<owner/repo>
# Read repo conventions
REPO_DIR=$(echo "$REPO" | cut -d/ -f2)
for DIR in ~/$REPO_DIR ~/projects/$REPO_DIR ~/.hermes/$REPO_DIR ~/clawd/$REPO_DIR /tmp/$REPO_DIR; do
[ -d "$DIR/.git" ] && LOCAL_DIR="$DIR" && break
done
# Create worktree under the shared worktree directory, never /tmp.
# This keeps work discoverable and avoids losing state between sessions.
# Prefer a repo+PR-specific path so babysit jobs don't collide with branch-named
# worktrees from other repos or follow-up PRs.
BRANCH=$(gh pr view $PR --repo $REPO --json headRefName -q '.headRefName')
WORKTREE="$HOME/projects/_worktrees/${REPO_DIR}-pr-${PR}"
mkdir -p "$HOME/projects/_worktrees"
if [ -d "$WORKTREE" ] && git -C "$WORKTREE" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
# Shared worktree already exists. It may have been created from a different local clone,
# so do not rely only on LOCAL_DIR's worktree list.
cd "$WORKTREE"
elif [ -n "$LOCAL_DIR" ]; then
# If this branch already has a worktree in this clone, use it instead of creating a second one.
EXISTING=$(git -C "$LOCAL_DIR" worktree list --porcelain | awk -v branch="refs/heads/$BRANCH" '
/^worktree / { wt=$2 }
$0 == "branch " branch { print wt }
' | head -1)
if [ -n "$EXISTING" ]; then
WORKTREE="$EXISTING"
else
git -C "$LOCAL_DIR" fetch origin "$BRANCH"
git -C "$LOCAL_DIR" worktree add "$WORKTREE" "origin/$BRANCH" 2>/dev/null || \
git -C "$LOCAL_DIR" worktree add --detach "$WORKTREE" "origin/$BRANCH"
fi
cd "$WORKTREE"
else
# No local clone found — clone directly to the worktree path using gh auth fallback.
gh repo clone "$REPO" "$WORKTREE" -- --branch "$BRANCH"
LOCAL_DIR="$WORKTREE"
cd "$WORKTREE"
fi
# Read project rules
for F in CLAUDE.md AGENTS.md; do
[ -f "$WORKTREE/$F" ] && cat "$WORKTREE/$F"
done
Scope Check (runs once, before the loop)
Before fixing anything, verify the PR's changes match its stated purpose. This catches accidental commits, formatting noise, and scope creep.
# Get PR metadata
gh pr view $PR --repo $REPO --json title,body,commits --jq '{title: .title, body: .body, commits: [.commits[].messageHeadline]}'
# Get changed files (--stat is not a valid gh flag; use --name-only)
# For very large PRs, GitHub may return HTTP 406 because the diff exceeds its file cap
# (often ~300 files). In that case, use the Pull Request Files API (paginated) or a
# local git diff in the isolated worktree.
gh pr diff $PR --repo $REPO --name-only || \
gh api --paginate "repos/$REPO/pulls/$PR/files?per_page=100" --jq '.[].filename'
# Local fallback when you need authoritative file status for large PRs:
# BASE=$(gh pr view $PR --repo $REPO --json baseRefName -q '.baseRefName')
# git -C "$WORKTREE" fetch origin "$BASE"
# git -C "$WORKTREE" diff --name-status "origin/$BASE...HEAD"
# Get individual commit messages and their file lists
for SHA in $(gh api "repos/$REPO/pulls/$PR/commits" --jq '.[].sha'); do
echo "--- Commit ${SHA:0:8} ---"
gh api "repos/$REPO/commits/$SHA" --jq '.commit.message'
gh api "repos/$REPO/commits/$SHA" --jq '[.files[].filename] | join(", ")'
done
Evaluate:
- File relevance: Do all changed files relate to the PR title/description? Flag files that seem unrelated (e.g., a "fix login" PR that also reformats unrelated templates).
- Commit coherence: Does each commit message align with the PR's purpose? Flag commits that introduce unrelated work.
- Formatting noise: Flag bulk formatting changes (ruff, prettier, eslint --fix) applied beyond the files the PR actually needs to touch.
- Scope creep: Multiple distinct features or fixes bundled into one PR. Each PR should do one thing.
If scope issues found:
Report them with specifics (which files, which commits) and classify:
- MINOR: A stray formatting commit or one unrelated file. Note it but continue babysitting.
- MAJOR: The PR bundles multiple unrelated changes, has bulk formatting noise, or commits that contradict the description. ESCALATE. Do not auto-fix. Report what should be split out or reverted.
Include scope findings in every status report so the parent/user sees them.
The Loop
Repeat up to max_cycles times:
1. Wait for CI
Poll CI status every 60 seconds until all checks complete or 20 minutes pass (whichever comes first).
cd "$WORKTREE"
gh pr checks $PR --repo $REPO
States:
- All green → go to step 2 (check reviews)
- Failures → go to step 3 (analyze and fix)
- Still running after 20 min → report status, continue waiting (reset timer)
- No checks at all → go to step 2
2. Check Reviews
Always read ALL comments and reviews, even when CI is green. Automated reviewers (claude-review, Seer, Bugbot) post findings as issue comments or review bodies that may flag real issues despite passing CI.
cd "$WORKTREE"
# 1. Inline review comments (on specific lines of code)
gh api --paginate "repos/$REPO/pulls/$PR/comments" | \
jq '.[] | {author: .user.login, path: .path, line: .line, body: .body, commit: .original_commit_id, created: .created_at}'
# 2. Issue comments (automated reviewers post here)
gh api --paginate "repos/$REPO/issues/$PR/comments" | \
jq '.[] | {author: .user.login, body: .body, created: .created_at}'
# 3. Review verdicts
gh api --paginate "repos/$REPO/pulls/$PR/reviews" | \
jq '.[] | {author: .user.login, state: .state, body: .body}'
# Current HEAD for staleness check
HEAD=$(gh pr view $PR --repo $REPO --json headRefOid -q '.headRefOid')
Staleness check: Compare each comment's original_commit_id (or created_at) against HEAD. If a comment was made on an older commit, verify the issue still exists in the latest code before acting.
Triage review findings: For each issue flagged by reviewers (human or automated), classify it as BLOCKING, DEFERABLE, ADDRESSED, or HUMAN-INTENT per step 3. Blocking issues are fixed on the current PR. Deferable issues do not block the current PR, but they must become real follow-up PRs so they are not forgotten.
Resolve handled comments: After triaging, resolve any review threads that are outdated or already addressed. See step 2b.
Before declaring ready, run the automated review final sweep. If CI is green but GitHub still shows CHANGES_REQUESTED, BLOCKED, a latest top-level bot comment with Must Fix items, or unresolved non-outdated GraphQL threads, keep working unless every remaining thread is deferable and has a linked follow-up PR. Load references/automated-review-final-sweep.md for exact commands and decision rules.
If CI green + no blocking unaddressed findings + no unresolved non-outdated blocking threads + every deferable thread has an opened follow-up PR → PR is ready. Report success and stop.
If there are blocking or deferable findings → go to step 3.
2b. Resolve Outdated / Addressed Comments
After reading all comments, check each unresolved review thread to determine if it's been addressed by subsequent commits or is no longer applicable. Use the GraphQL API to fetch threads and resolve them.
# Fetch all unresolved review threads with their comments
gh api graphql \
-f query='query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
isOutdated
path
line
comments(last: 50) {
nodes {
body
author { login }
createdAt
originalCommit { oid }
}
}
}
}
}
}
}' \
-f owner="$(echo $REPO | cut -d/ -f1)" \
-f name="$(echo $REPO | cut -d/ -f2)" \
-F number=$PR
Note: first: 100 covers the vast majority of PRs. For exceptionally large PRs with 100+ threads, add pageInfo { hasNextPage endCursor } and paginate with after: cursor.
For each unresolved thread, evaluate:
- Already fixed: The issue raised in the comment has been addressed by a subsequent commit. Read the current file at that path/line and confirm the fix is present.
- Outdated by refactor: The file or lines the comment refers to no longer exist or have been substantially rewritten.
- Deferable: The issue is a low-priority non-bug: style, naming, docs, test ergonomics, small cleanup/refactor, dedupe, logging polish, non-critical perf, or an improvement that does not affect correctness.
- Human-intent: The thread asks a product/design/API question or presents multiple valid approaches.
- Still blocking: The issue persists and could affect correctness, security/privacy, data integrity, deployability, tests, or API contracts. Leave unresolved and either auto-fix (step 3) or escalate.
For automated-reviewer threads that an author already declined or explained, verify against live PR HEAD before editing. Read the referenced current lines and adjacent implementation, run the narrowest relevant test/probe if useful, and decide whether the finding is still actionable. If live code supports the author's explanation, do not push a cosmetic/no-op change just to appease the bot. Reply with the HEAD SHA and concrete evidence, then resolve the thread via GraphQL (addPullRequestReviewThreadReply + resolveReviewThread). After resolving, re-query reviewThreads and checks to prove unresolved=0, CI/status rollup is success, review decision/merge state are clean, and the worktree stayed unmodified.
Pitfall: if a shell pipeline that pipes gh api graphql into python - <<'PY' fails because the heredoc consumes stdin, retry using gh api --jq for simple thread counts instead of treating the API result as unavailable.
For threads that are addressed or outdated:
- Reply to the thread explaining why it's resolved (be specific, cite the commit or line change):
# Reply to the review thread
gh api graphql \
-f query='mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: $threadId,
body: $body
}) {
comment { id }
}
}' \
-f threadId="<THREAD_ID>" \
-f body="Resolved: <brief explanation of what changed>"
- Resolve the thread:
# Resolve the review thread
gh api graphql \
-f query='mutation($threadId: ID!) {
resolveReviewThread(input: {
threadId: $threadId
}) {
thread { isResolved }
}
}' \
-f threadId="<THREAD_ID>"
Reply templates:
- Fixed by commit:
"Resolved -- fixed in {short_sha}: {what changed}"
- Outdated by refactor:
"Resolved -- this code was refactored/removed in {short_sha}"
- Informational/acknowledged:
"Acknowledged -- {brief response to the observation}"
- Already correct:
"Resolved -- verified this is already handled: {evidence}"
Rules for resolving:
- Only resolve threads where you have high confidence the issue is addressed. When in doubt, leave it open.
- Never resolve threads from human reviewers that are asking questions. Those need a human answer.
- Always reply before resolving so there's a paper trail of why it was closed.
- Bot/automated reviewer threads (claude-review, Seer, Bugbot, etc.) can be resolved freely if the issue is demonstrably fixed.
- **Deferable bot threads may be replied to and resolved only after you have opened a concrete follow-up PR and can link it. Reply:
Deferred to follow-up PR #{n}: {what it fixes}.
- If the prompt says an existing follow-up PR may cover deferable threads, verify it against the actual unresolved thread bodies and the follow-up diff before linking/resolving. Titles and PR bodies can be stale or only partially overlapping. If the existing follow-up does not address every cited behavior, create a new focused stacked follow-up from the source PR head, prove the regression red/green, link that new PR in the relevant threads, then resolve them.
- **Deferable human threads should get a reply with the follow-up PR link. Resolve them only if the reviewer clearly framed it as non-blocking (
nit, optional, follow-up, approval/praise) or the user explicitly told you to resolve it.
- Keep replies concise. One sentence with the commit SHA, evidence, or follow-up PR link.
3. Analyze and Decide
For each issue (CI failure or review comment), classify it:
BLOCKING AUTO-FIX (all must be true):
- Root cause is clear (not just the symptom)
- Fix is unambiguous (one correct approach)
- Fix is small and surgical (not a refactor or design change)
- You can verify it locally (run the test, check the lint)
Examples: typos, missing imports, lint failures, simple logic bugs, null checks, formatting.
DEFERABLE FOLLOW-UP PR (all must be true):
- The current PR is safe to merge without it
- The comment is low priority: style, naming, docs, test ergonomics, small cleanup/refactor, dedupe, logging polish, non-critical perf, or nice-to-have improvement
- The fix can be made as a separate PR without changing the correctness of the current PR
- The follow-up can be described in one coherent PR title/body
Do not merely list deferable items. Open the follow-up PR so it is tracked.
ESCALATE (any of these):
- Multiple valid approaches; you'd be guessing
- Design decision, API change, or architectural issue
- Requires new dependencies, config changes, or DB migrations
- Flaky/infrastructure CI failure (retry the run instead of pushing code)
- You already tried to fix this exact issue in a previous cycle and it didn't work
3b. Open Follow-up PRs for Deferable Comments
When only low-priority non-bug review comments remain, do not fix them on the current PR. Create one or more follow-up PRs and link them back to the original review threads.
Grouping rule: one follow-up PR per coherent change. Group related nits together (for example docs wording), but do not bundle unrelated cleanup with unrelated test ergonomics.
Base/branch rule:
- If the original PR is still open, its code is not on the default branch yet, and the PR head branch lives in the base repository, branch from the original PR HEAD and open a stacked follow-up PR with
--base ORIGINAL_PR_BRANCH. This keeps the current PR unchanged while making the follow-up impossible to forget.
- If the original PR is from a fork,
headRefName is not a branch on the base repo's origin, and the new code may not exist on the default branch yet. Prefer to defer the follow-up until the original PR merges (then branch from the default branch). Only branch from the fork head directly if you have write access to it and create the follow-up with --head OWNER:BRANCH.
- If the original PR is already merged or the default branch already contains the relevant code, branch from the default branch and open the follow-up PR against the default branch.
- Never push follow-up fixes onto the current PR branch unless the user explicitly asks to fix the current PR instead.
Procedure:
# Original PR metadata
BRANCH=$(gh pr view $PR --repo $REPO --json headRefName -q '.headRefName')
HEAD_OWNER=$(gh pr view $PR --repo $REPO --json headRepositoryOwner -q '.headRepositoryOwner.login')
BASE_OWNER=$(echo "$REPO" | cut -d/ -f1)
DEFAULT=$(gh repo view $REPO --json defaultBranchRef -q '.defaultBranchRef.name')
STATE=$(gh pr view $PR --repo $REPO --json state -q '.state')
# Choose base. Stack only when the source branch is on the base repo; fork heads
# are named OWNER:BRANCH for PR creation and are not fetchable as origin/BRANCH.
BASE="$BRANCH"
if [ "$STATE" = "MERGED" ] || [ "$HEAD_OWNER" != "$BASE_OWNER" ]; then
BASE="$DEFAULT"
fi
# Create a new worktree for the follow-up branch.
# Set a real per-group slug so multiple follow-ups don't collide on one branch/worktree/body-file.
SHORT_TOPIC="docs-wording" # replace per coherent group, e.g. test-ergonomics
FOLLOW="followup-pr-$PR-$SHORT_TOPIC"
git fetch origin "$BASE"
git worktree add "$HOME/projects/_worktrees/$FOLLOW" -b "$FOLLOW" "origin/$BASE"
cd "$HOME/projects/_worktrees/$FOLLOW"
# Apply the deferable cleanup, verify, commit, push
git add FILES
git commit -m "followup: <short topic>"
git push origin HEAD:"$FOLLOW"
# Open the follow-up PR. Write body to a file, never inline long markdown.
gh pr create --repo "$REPO" --base "$BASE" --head "$FOLLOW" \
--title "Follow up to #$PR: <short topic>" \
--body-file /tmp/followup-pr-$PR-$SHORT_TOPIC.md
Follow-up PR body must include:
Follow-up to #<original PR>
- Links or quoted summaries for the deferred review comments
- Why it was safe to keep the original PR moving
- Verification run for the follow-up PR
After creating the follow-up PR, reply to each deferred thread with the PR link:
Deferred to follow-up PR #<n>: <one-line summary>. This is non-blocking for the current PR because <reason>.
4. Fix
If blocking auto-fixable issues exist:
- Pull latest and check if already fixed:
cd "$WORKTREE" && git fetch origin $BRANCH && git log --oneline origin/$BRANCH -5. If remote is ahead of your local HEAD, inspect the new commits — someone (or a prior agent run) may have already fixed the issue. Verify by reading the flagged file at origin/$BRANCH. If already fixed, skip to step 5.
1b. Rebase onto remote: git pull --rebase origin $BRANCH (or merge if rebase isn't clean)
- Read the relevant files in full (not just the diff)
- Make the minimal, targeted fix
- Verify locally using whatever lint/test commands the project's CLAUDE.md or AGENTS.md specifies. Run the specific failing test if identifiable. If local pytest fails before collection because the repo's
addopts references an unavailable plugin (for example -n auto without pytest-xdist), rerun the targeted test with an explicit override such as python -m pytest -o addopts='' <test> and report both the local tooling limitation and the successful targeted command; still rely on live CI for the full configured command.
- Stage carefully:
cd "$WORKTREE" && git add -A && git diff --cached --stat — review the staged file list. Setup commands (make setup-worktree, uv sync, bun install) can generate untracked files (e.g. config/mcporter.json, .env, node_modules/ artifacts) that git add -A will pick up. Unstage anything not part of your fix: git reset HEAD <file>.
- Single commit:
cd "$WORKTREE" && git commit -m "fix: <description>"
- Push:
cd "$WORKTREE" && git push origin HEAD:$BRANCH
One commit per cycle. Don't stack multiple speculative fixes.
5. Loop or Stop
After pushing (or deciding not to):
Continue looping if:
- You just pushed a fix (need to wait for new CI run and any reviewer re-run)
- You just opened a follow-up PR for deferable comments and still need to reply/link/resolve the original threads
- A reviewer bot is still running, even if prior CI/checks were green
- A fresh reviewer run surfaced new actionable findings after earlier fixes. Treat that as the next cycle, not as churn to ignore
- There are still issues you plan to address next cycle
Stop and report if:
- PR is ready (latest CI green, latest reviewer run completed, no blocking unaddressed comments, deferable comments have opened follow-up PRs, scope is tight)
- You hit max_cycles
- All remaining issues need human input (escalate)
- You pushed a fix for the same issue twice and it still fails (circuit breaker)
- Scope check found MAJOR issues (escalate immediately, don't try to fix)
Reporting
Send progress updates to the parent agent via send_to_task. Do NOT try to send messages to Signal/Slack/etc directly (sub-agents don't have channel access). The parent agent handles delivery to the user.
If no code change was needed, explicitly report Pushed commit SHA(s): none plus already-clean evidence: live head SHA, aggregate PR state, checks, latest substantive review/comment, unresolved non-outdated thread count, and worktree path. Do not invent a pushed SHA or create empty commits just to satisfy a SHA request.
If a parent session key was provided, use:
send_to_task(sessionKey="<parent_session_key>", message="<status update>")
If no parent session key was provided, include status in your final output text (the auto-announce will deliver it).
When to report:
- After scope check (always, even if clean)
- After each fix push (brief: what was fixed)
- When escalating (what needs human input and why)
- When the PR is ready (final status)
Signal/chat discipline:
- Do not stream raw scratchpad, grep results, or "let me check" narration into Signal/Slack. Users read those as confusing status leaks.
- Send only milestone updates: scope result, fix pushed, CI status, blocker, ready. If you need to think aloud, keep it internal and finish the tool cycle before messaging.
Format:
🔧 PR #{number} ({repo}) — Cycle {N}/{max}
Scope: ✅ clean | ⚠️ minor (details) | 🚫 major (details)
Fixed: <what you fixed>
Resolved: <N threads resolved with reasons>
Follow-ups: <opened PR links for deferable comments, or none>
Waiting: <what CI is running>
Needs attention: <what you can't fix and why>
Status: <monitoring | ready | blocked | scope-drift>
When PR is ready:
✅ PR #{number} ({repo}) — Ready to merge
Scope: ✅ changes match description
CI: all green
Reviews: no blocking findings
Commits: {count}
Follow-ups: <none | #follow-up-pr links>
When scope drift detected:
⚠️ PR #{number} ({repo}) — Scope Drift
Description says: <what PR claims to do>
Actually includes:
- <unrelated file/commit 1>
- <unrelated file/commit 2>
Recommendation: <split into N PRs | revert commits X,Y | remove files A,B>
Cleanup
When done (success or giving up):
cd ~
git -C "$LOCAL_DIR" worktree remove "$WORKTREE" --force 2>/dev/null
Multi-PR Babysitting
When asked to babysit multiple PRs interactively, do the work directly in the parent agent rather than spawning sub-agents. Sub-agent delegation for babysit-pr has a high failure rate (toolset mismatches, context loss, wasted tokens). Even with correct toolsets, sub-agents often fail on the skill-loading chain. Work through PRs sequentially:
- Check CI + reviews on all PRs first (quick
gh pr checks loop + mergeable status)
- Triage: which need blocking fixes vs. which only need follow-up PRs vs. which are already merged
- Fix blocking issues in each PR in order, push, move to next
- For deferable comments, open stacked follow-up PRs (or default-branch follow-ups if the source PR is merged), link them in the original threads, then move on
- Circle back if any need a second cycle after CI re-runs
This is faster and more reliable than parallel sub-agents for 2-5 PRs.
Shared-failure shortcut: Before diving into per-PR fixes, check if all PRs share the same CI failure (e.g., rate-limited reviewer bot, expired token, infra flake). If so, diagnose once, report the common cause, and skip redundant per-PR analysis. Common patterns: reviewer-bot rate limits ("You've hit your limit"), GitHub App auth failures, shared secret expiry.
For scheduled "babysit all open PRs" cron jobs, load references/daily-open-pr-babysitter.md. If the prompt explicitly asks for parallel delegation, batch according to the runtime's real concurrency cap, then run a parent-agent verification sweep before the digest. Child summaries are not enough: subagents often time out after pushing useful fixes or leave resolvable bot threads open.
Cron mass-babysit handoff protocol: In large scheduled runs, treat each delegation batch as a partial transaction, not a terminal verdict. After every batch, rerun the collector, compare pre/post head SHAs, inspect subagent worktrees for local commits or dirty files, push verified local fixes yourself if a child timed out, resolve fixed bot threads while the SHA is fresh, and only then continue. For scheduled "fix all open PRs" jobs, 0 need attention is not done if the collector still reports ready_with_followups or any clean:false entry. Follow-up PRs opened during babysitting are part of the same transaction and must be babysat to clean/approved/green before final reporting.
Quick Fix Mode (no full babysit loop)
When the user says "fix comments on PR X" or "fix merge conflicts on PR X", skip the full babysit loop. Just:
- Check the PR state first (
gh pr view --json state,mergedAt,headRefOid). If it is already merged, do not push more commits expecting that PR to update. Treat actionable unresolved threads as follow-up work on a fresh branch from current base.
- Read every latest review source, including GraphQL review threads. Merged/approved PRs can still have unresolved non-outdated bot threads from a later review pass.
- Check if issues are already fixed in current code (grep the worktree, don't assume)
- Fix what's actionable, commit, push
- If the original PR is merged, open a new follow-up PR and reply/resolve the original threads with the follow-up PR link. If it is open, resolve addressed threads in bulk on that PR.
- Report what was fixed and what needs attention
This is the common case: user already knows what's wrong, just wants it done. The trap is assuming "approved" or "merged" means there are no actionable comments left; verify the thread list anyway.
Gotchas
See also:
-
references/delegation-and-git-pitfalls.md — force-push recovery, batch thread resolution, toolset names
-
references/public-repo-redaction.md — stripping hardcoded values from public repos and adding env vars
-
references/branch-preservation-and-ci-auth.md — preserving dirty branches via commit+patch pattern, CI 401 auth retry protocol, public repo redaction checklist
-
references/automated-review-final-sweep.md — final reconciliation of CI, formal reviews, issue comments, and unresolved GraphQL threads before calling a PR clean
-
references/deferable-comments-followup-prs.md — policy for low-priority review comments: open real follow-up PRs and link them, never leave recommendations as TODOs
-
Python mock failures in xdist. String-based patch("pkg.submod.func") can silently fail in pytest-xdist workers if the submodule isn't explicitly imported. Use patch.object(imported_mod, "func") instead. Also patch the import site that the view/code under test actually calls, not the original service module, when functions are imported with from service import func; otherwise xdist/order-dependent tests can leak stubs or miss the patch. See references/delegation-and-git-pitfalls.md for the full diagnosis checklist.
-
caplog can be flaky under full-suite xdist. If CI fails with caplog.text == "" even though a focused local test passes, don't keep tweaking logger levels. Two proven alternatives:
- Mock the logger methods when the test promises specific log content:
mock_warning = Mock(); monkeypatch.setattr(module.logger, "warning", mock_warning), then assert any("expected substring" in str(call) for call in mock_warning.call_args_list).
- Side-effect list when the test only needs to confirm the graceful-skip path ran (not the exact message):
close_calls = []; monkeypatch.setattr(module, "close_old_connections", lambda: close_calls.append("closed")), then assert close_calls.
Choose (1) for tests named *_treats_X_as_Y or *_downgrades_X; choose (2) for simpler "did it skip or raise" assertions. Use a mixed/uppercase fixture message when the fix is case-insensitive string matching, so the regression test actually proves .lower() is used.
-
Do NOT
- Post top-level PR comments (triggers claude-review re-runs, wastes tokens). Replying to existing review threads in step 2b is fine — those don't trigger CI.
- Merge the PR (the repo owner merges)
- Force push or rewrite history
- Make changes unrelated to the PR's purpose
- Fix more than one issue per commit
- Retry the same fix approach twice
- Auto-fix scope drift (always escalate it)