
Pr Babysitter
- 208 installs
- 74 repo stars
- Updated August 5, 2026
- mblode/agent-skills
Babysit open pull requests by watching CI, fixing failures, addressing review threads, and resolving merge conflicts until stacks are green and merge-ready.
About
pr-babysitter from agent-skills automates pull request hygiene: it watches CI, responds to review feedback, fixes failing checks, resolves conflicts, and keeps GitHub stacks merge-ready without constant manual babysitting.
- CI failure triage and fixes
- Review comment responses
- Merge conflict resolution
- Stacked PR monitoring
- Merge-ready status tracking
Pr Babysitter by the numbers
- 208 all-time installs (skills.sh)
- +20 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #178 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mblode/agent-skills --skill pr-babysitterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 208 |
|---|---|
| repo stars | ★ 74 |
| Last updated | August 5, 2026 |
| Repository | mblode/agent-skills ↗ |
What it does
Babysit open pull requests by watching CI, fixing failures, addressing review threads, and resolving merge conflicts until stacks are green and merge-ready.
Files
PR Babysitter
Autonomous monitor for an open PR. Detects the PR from the current branch, polls every 2 minutes, fixes what it can, and speaks only when state changes.
- IS: autonomous monitoring of an open PR (merge conflicts, CI across GitHub Actions, Buildkite, Vercel, and Fly.io, review comments, merge readiness) with auto-fixes, plus one-shot CI diagnosis or conflict resolution.
- IS NOT: creating the PR (use
pr-creator), reviewing the diff for bugs (usepr-reviewer), or npm release pipelines (useautoship, which watches its own release CI; never start a babysitter on a release or Version Packages PR that autoship is driving).
Mode Selection
| Invocation | Mode |
|---|---|
| "babysit", "watch this PR", "monitor", "keep it green" | Monitor: Phase 1 once, then phases 2-5 on every cron tick |
| "fix CI", "why is CI red", "CI is broken", "loop on CI" | One-shot Phase 3 loop, no cron |
| "resolve conflicts", "fix conflicts" | One-shot Phase 2, no cron |
| "triage review comments", "address the comments" | One-shot Comment Triage Workflow, no cron |
Rules that apply in every mode:
- No setup questions. Auto-detect the PR, platforms, and defaults; start immediately. Overrides arrive inline only ("poll every 5 minutes", "enable auto-merge").
- Skip closed or merged PRs. Skip draft PRs unless the user explicitly asks.
- Comment triage runs autonomously inside the cycle, with no plan approval gate.
Reference Files
| File | Read when |
|---|---|
references/monitoring-setup.md | Starting monitor mode: CronCreate config, state file format, defaults |
references/merge-conflicts.md | Phase 2: mergeStateStatus table, rebase workflow, auto-resolvable file types |
references/ci-platforms.md | Phase 3: per-platform log and retry commands, Buildkite auth fallback chain, failure classification, stale-dependency and knip handling |
references/github-api.md | Comment triage: GraphQL queries for fetching, replying to, and resolving threads |
references/bot-patterns.md | Comment triage: bot detection, severity parsing, deduplication, false-positive rules |
references/fix-plan-template.md | Comment triage: the audit-trail plan document format |
references/verification-gate.md | Before any commit or push: lint, type-check, test, knip gate, stray-artifact sweep |
references/git-resilience.md | Any git command hangs or fails transiently (fsmonitor wedge, stale index.lock, IPC blip) |
Monitor Loop
Phase 1 runs once in the foreground and registers the cron job. Each tick then runs phases 2-5 in order: read the state file, check conflicts, check CI, check comments, evaluate readiness, write the state file. Every notification is a transition against the previous tick's state; a quiet poll produces no output.
Copy this checklist to track progress:
PR babysit progress:
- [ ] Phase 1: Initialize (auto-detect PR, snapshot state, start cron)
- [ ] Phase 2: Conflict check (detect and resolve merge conflicts)
- [ ] Phase 3: CI/CD check (poll checks, diagnose failures, fix and push)
- [ ] Phase 4: Comment check (detect new comments, triage autonomously)
- [ ] Phase 5: Readiness check (evaluate merge readiness, notify user)Phase 1: Initialize
Load references/monitoring-setup.md for CronCreate configuration and defaults.
1. Auto-detect the PR: gh pr view --json number,url,title,headRefName,baseRefName,mergeable,mergeStateStatus,reviewDecision. If a PR number was passed as an argument, use it directly. If no PR exists for the branch, say so and stop. 2. Extract owner/repo: gh repo view --json owner,name 3. Snapshot state to .claude/scratchpad/babysit-pr-{N}.md: HEAD SHA, mergeable status, check statuses, unresolved thread count, review decision 4. Detect CI platforms from gh pr checks check names (dispatch table in Phase 3) 5. Create cron job: CronCreate with */2 * * * * running phases 2-5. Print a single confirmation:
Monitoring PR #{N}: {title}
Polling every 2 minutes | Auto-resolve noise: yes | Auto-merge: no
Detected CI: {platforms}
Current state: {mergeable} | {reviewDecision} | {check_summary}Phase 2: Conflict Check
Load references/merge-conflicts.md for the mergeStateStatus table and resolution strategy.
1. Check mergeable status: gh pr view --json mergeable,mergeStateStatus
MERGEABLEand up to date → skip to Phase 3CONFLICTING→ resolveUNKNOWN→ GitHub is still computing; recheck next tick
2. Attempt rebase: git fetch origin {base_branch} && git rebase origin/{base_branch}
- Clean rebase →
git push --force-with-lease→ notify - Conflicts only in safe files (lockfiles, generated files, changelogs) → auto-resolve per the reference, push
- Logic conflicts in source →
git rebase --abort→ notify with the conflicting files and what each side changed
Never push with bare --force. A failed --force-with-lease means someone else pushed; abort and notify rather than overwrite their commits. If git fetch or git rebase hangs, see references/git-resilience.md.
Phase 3: CI/CD Check
Load references/ci-platforms.md for full per-platform commands, the Buildkite auth fallback chain, and the failure-classification decision tree.
1. Poll: gh pr checks --json name,state,conclusion,detailsUrl 2. Classify each check: passing, pending (wait for completion before diagnosing), or failing 3. All passing → proceed to Phase 4 4. Failing → dispatch on check name to fetch logs:
| Check name / detailsUrl | Platform | Failure logs via |
|---|---|---|
buildkite/ prefix | Buildkite | Auth fallback chain: bk CLI, then REST API, then detailsUrl |
vercel in name or vercel.com in URL | Vercel | vercel logs {deployment_url} |
fly- prefix or fly.io in URL | Fly.io | flyctl logs --app {app_name} --no-tail |
| Anything else | GitHub Actions | gh run view {run_id} --log-failed |
5. Classify the failure per the decision tree: flaky (re-run), stale dependency (reinstall and rebuild before touching source), code error (fix), knip (remove dead code or configure), infrastructure (notify; not fixable from code) 6. Fix, gate, push: run the verification gate (references/verification-gate.md) locally before any push 7. Compare with previous state: flag regressions (previously passing, now failing)
One-shot loop ("fix CI"): after pushing a fix, run gh pr checks --watch and re-diagnose if still red. Exit when all checks are green (report the green run), when the failure is infrastructure, or when the same check fails twice with the same error after a fix attempt; then summarize the diagnosis instead of thrashing.
Phase 4: Comment Check
1. Count unresolved threads: GraphQL count via references/github-api.md 2. Compare with the state file. New unresolved threads since last tick → notify "N new review comments on PR #{N}" and run the Comment Triage Workflow below 3. Auto-resolve noise: resolve unambiguous noise bots (vercel, linear, changeset linkbacks) with a one-line reason. Never auto-resolve human comments or critical/major findings
Phase 5: Readiness Check
1. Ready means all of: mergeable == MERGEABLE, all required checks passing, reviewDecision == APPROVED, zero unresolved blocking threads 2. Ready → notify: "PR #{N} is ready to merge. All checks green, reviews approved, no conflicts." Do not merge; auto-merge requires explicit opt-in 3. Not ready → report blockers: "Waiting on: 2 checks pending" / "Blocked by: merge conflict" 4. Notify only on transitions: check went green or red, new review, conflict appeared or cleared, all clear 5. Write the state file so the next tick can diff against it
Comment Triage Workflow
Runs inline when Phase 4 finds new comments, or one-shot when invoked directly. No plan approval; the plan file is an audit trail.
Load references/github-api.md for query templates and references/bot-patterns.md for detection rules.
Fetch
1. Review threads: GraphQL reviewThreads query with pagination; filter to isResolved == false 2. PR reviews: REST reviews endpoint (state, body, author) 3. Issue-level comments: REST endpoint for PR conversation comments 4. Early exit if zero unresolved threads, zero actionable reviews, and zero actionable issue comments
Classify
1. Author type: human or bot. Bots classify by content first, then username (github-actions[bot] is a shared identity) 2. Skip noise per the bot-patterns reference 3. Severity: parse bot-specific markers; humans default to Major for CHANGES_REQUESTED, Minor for APPROVED plus a question 4. Deduplicate: comments on the same file within a 3-line range are one issue; keep the highest severity. Never deduplicate human comments 5. Disposition: category, severity, confidence, then fix or ignore with a stated reason
Human comments are never auto-ignored. Classify as fix unless clearly already resolved or the reviewer explicitly marked it optional.
Fix
1. Write the plan to .claude/scratchpad/pr-{N}-review-plan.md per references/fix-plan-template.md, as the audit trail 2. Print counts (N to fix, K conversation items, M ignored) and proceed immediately 3. Resolve ignored threads: post a brief reply, then resolve via GraphQL 4. Fix real issues grouped by commit group; parallelize independent file fixes 5. Gate, commit, push: the verification gate (references/verification-gate.md) must pass; sweep stray artifacts (e.g. a root schema.gql left by a hook); one commit per logical group, staging only that group's files 6. Reply and resolve each fixed thread via GraphQL 7. Verify: re-fetch threads and report zero unresolved remaining (or list what remains), plus current CI status
Stopping
- "Stop babysitting" / "cancel the PR monitor" → CronDelete using the job ID from the state file
- PR merged or closed → detected on the next tick, self-cancel
- Session exit → jobs are session-scoped and auto-clean
On stop, report a final summary: total polls, fixes applied, conflicts resolved, comments triaged, current state.
Gotchas
- Asking setup questions before starting: every question defeats the point of an autonomous monitor. Auto-detect, apply defaults, start.
git push --forceinstead of--force-with-lease: silently overwrites a teammate's commits. A failed lease means someone pushed; abort and notify.- Auto-resolving or auto-ignoring human comments: reviewers re-open the threads and stop trusting the monitor. Humans always classify as fix unless marked optional.
- Resolving a thread without posting a reply first: the reviewer sees a silent resolve with no reasoning and unresolves it.
- Fixing items the triage classified as ignore: produces churn nobody asked for and contradicts the audit trail.
- One commit per individual comment: makes review history unreadable. Group related fixes by commit-group label.
- Pushing before the verification gate (lint, type-check, test,
knip) passes locally: a red push wastes the whole poll cycle plus a CI run. - Committing stray hook artifacts (e.g. a root
schema.gql): pollutes the PR diff. Sweepgit status --porcelainand stage only the fix's files. - Treating a monorepo type-check failure as a code bug: it is often stale deps or generated types. Reinstall and rebuild first; edit source only if it persists.
- Aborting the monitor on one hung or transient git command: fsmonitor wedges and stale locks are recoverable (
references/git-resilience.md). Retry before giving up. - Re-diagnosing while checks are still pending: you diagnose a half-finished run and fix the wrong thing. Wait for completion.
- Polling faster than every 2 minutes: burns GitHub API rate limit for no signal. 2 minutes is the floor.
- Notifying on every poll with no state change: notification fatigue trains the user to ignore the monitor. Only transitions speak.
- Auto-merging without explicit user opt-in: merge is a one-way door. "Ready to merge" is a notification, not an action.
- Classifying
github-actions[bot]as always noise: it is a shared identity used by DangerJS, schema checkers, and other active reviewers. Classify by content. - Using the
bkCLI without checkingbk auth statusfirst: Keychain tokens expire, and a dead token stalls the cycle. Fall back to the REST API orgh pr checks.
Related Skills
pr-creator: opens the PR; babysitting starts after it existspr-reviewer: local diff review for bugs; run it on monitor-authored fixes that grow beyond a trivial patchautoship: npm release pipelines; it watches its own release CI, so never babysit a release PR autoship is driving
Bot Patterns
Detection, severity parsing, deduplication, and false positive rules for PR review bots.
Contents
- Bot classification
- Shared identity: github-actions bot
- Active review bots
- Noise bots
- Human review patterns
- Severity normalization
- Deduplication
- False positive detection
Bot classification
Bots cannot be classified by username alone. github-actions[bot] is a shared identity used by many workflows: some are active reviewers, others are pure noise. Classify by content first, then username.
| Tier | Behavior | Action |
|---|---|---|
| Active reviewer | Posts findings with severity or actionable suggestions | Parse and triage |
| Noise | Linkbacks, deployment status, CI notifications, rate limits | Always ignore |
| Check-only | Appears in PR checks but leaves no comments | Skip (nothing to triage) |
Shared identity: github-actions bot
github-actions[bot] is used by many different workflows. Match on comment content to determine which:
DangerJS: active reviewer
Detection:
- HTML comment contains
DangerID: danger-id-at the top of the body - HTML table uses
data-danger-table="true"attribute - Footer contains
Generated by :no_entry_sign: dangerJS
Severity: parse from the HTML comment metadata at the top:
<!--
0 failure:
1 warning: Multiple MFEs are...
DangerID: danger-id-Danger;
-->failurecount > 0 → majorwarningcount > 0 → minor- Table cells use
:warning:emoji for warnings
The comment body is an HTML table with actionable warnings. Treat these as real findings.
DangerJS updates its comment in place on each push: the same comment ID gets new content.
Schema compatibility checker: active reviewer
Detection:
- Heading contains a schema filename in
<code>tags (e.g.,## <code>profileShareIntended_2-0-2.schema.json</code>) - Contains
### ⚠️ Warningsor### 🔴 Errorssection headers - Table has columns: Field, Type, Status, Details
Severity markers in the Status column:
🟡 Warning→ minor🔴 Error→ major (likely present for breaking changes)> 💡 **Tip**:blockquotes are informational, not findings
Event-lib RC trigger: noise
Detection: single-line comment starting with "Event-lib RC version is triggered" followed by a Buildkite URL.
Changeset releases: noise
Detection: comment body starts with "This PR was opened by the [Changesets release]" or contains # Releases heading with version changelogs.
Auto-approval: noise
Detection: review with state APPROVED and empty body from github-actions[bot]. Skip: this is a programmatic approval from a passing workflow.
Active review bots
Devin (devin-ai-integration[bot])
Devin posts a PR review (state: COMMENTED) and may inject a badge into the PR body.
No findings (noise):
- Review body starts with
## ✅ Devin Review: No Issues Found - Followed by "View in Devin Review to see N additional findings", a teaser to their platform
- Classify as noise; the "additional findings" are paywalled
Findings (active reviewer):
- Review body starts with
## ⚠️ Devin Review: Issues Foundor similar - May include inline review comments with actual code findings
- Triage these like any other review finding
Badge injection (always ignore):
- Devin injects
<!-- devin-review-badge-begin -->/<!-- devin-review-badge-end -->into the PR body itself - These are
<picture>elements with dark/light mode SVGs linking toapp.devin.ai - Not a comment; it appears in the PR description. Ignore completely.
CI check: check-devin-approval GitHub Action gates merging on Devin's review. Not a comment; skip.
CodeRabbit (coderabbitai[bot])
May be active on other repositories. If present, parse inline review comments.
Severity markers use emoji in the comment body:
| Pattern | Severity |
|---|---|
🔴 or _🔴 Critical_ | Critical |
🟠 or _🟠 Major_ | Major |
🟡 or _🟡 Minor_ | Minor |
_⚠️ Potential issue_ (alone) | Major (default) |
Comment structure:
- Multi-section with collapsible
<details>blocks - "Analysis chain" section: skip (internal reasoning)
- Bold imperative summary: the actual finding
- "Proposed fix" section: diff block (
`diff) - "Prompt for AI Agents" section: extract this for fix guidance
Summary comments (PR-level, not inline) include walkthrough narratives and base64 metadata in HTML comments. These are not findings; skip.
Gemini Code Assist (gemini-code-assist[bot])
May be active on other repositories. If present, parse inline review comments.
Severity markers use SVG image references:
| Image URL pattern | Severity |
|---|---|
high-priority.svg | Critical |
medium-priority.svg | Major |
low-priority.svg | Minor |
Scan for gstatic.com/codereviewagent/ in image URLs.
Uses GitHub native `suggestion blocks with the exact proposed fix. Summary comment starts with "Hello @author, I'm Gemini Code Assist!"; skip it.
Noise bots
Always ignore; no actionable findings:
| Bot | Detection | What it posts |
|---|---|---|
linear[bot] | <!-- linear-linkback --> in body | Project management linkback to Linear ticket |
vercel[bot] | Body starts with [vc]: # | Deployment status tables with base64 metadata |
chatgpt-codex-connector[bot] | "You have reached your Codex usage limits" | Rate-limit messages only |
renovate[bot] | Author match | Dependency update descriptions, artifact failures |
dependabot[bot] | Author match | Dependency bump descriptions, @dependabot commands |
Check-only bots
These appear in gh pr checks but leave no comments. Nothing to triage:
- Cursor Bugbot: code review check from cursor.com, results only in check status
- mergefreeze: merge control gate, status only ("Ok to merge")
- Buildkite: CI pipeline checks (
buildkite/{project}/{step}) - Telemetry service attributes: service metadata validation from
blstrco/telemetry
Human review patterns
Humans don't use structured severity markers. Classify by review state and comment context:
| Pattern | Severity | Blocking? |
|---|---|---|
CHANGES_REQUESTED review with body text | Major | Yes (merge blocked) |
CHANGES_REQUESTED review, empty body + inline comments | Major | Yes |
COMMENTED review + inline question → then APPROVED seconds later | Minor | No (conversational) |
APPROVED review, empty body | n/a | No (skip) |
| Issue-level comment with soft language ("I'd also...", "but up to you") | Minor | No |
| Issue-level comment with directive language ("please use...", "must...") | Major | Depends on review state |
Human comments default to Major unless the language or context signals otherwise.
Severity normalization
Unified four-level scale:
| Source | Critical | Major | Minor | Nitpick |
|---|---|---|---|---|
| CodeRabbit | 🔴 | 🟠 | 🟡 | n/a |
| Gemini | high-priority | medium-priority | low-priority | n/a |
| DangerJS | n/a | failure count > 0 | warning count > 0 | n/a |
| Schema checker | n/a | 🔴 Error | 🟡 Warning | n/a |
| Devin (with findings) | n/a | Major (default) | n/a | n/a |
| Human (CHANGES_REQUESTED) | "critical"/"blocker" | Default | "nit"/"minor" | "nit"/"nitpick" |
| Human (APPROVED + question) | n/a | n/a | Minor (default) | n/a |
When multiple sources flag the same issue, use the highest severity.
Deduplication
Multiple bots may flag the same issue on overlapping lines.
Grouping rule: comments on the same path within a 3-line range likely address the same issue.
Within each group: 1. Keep the highest-severity comment 2. Prefer comments with a suggestion block or diff (most actionable) 3. Mark others as ignore-duplicate referencing the kept thread ID
Exceptions:
- Never deduplicate human comments; each gets its own entry
- Human + bot on the same issue: keep both (the human confirms the bot, increasing confidence)
False positive detection
Pre-existing code
The flagged line was not added or modified in this PR.
gh api "repos/{owner}/{repo}/pulls/{pr}/files" --paginateEach file includes patch with diff hunks. If the flagged line does not fall within an added range (+ lines), mark as ignore-pre-existing.
Irrelevant file types
Bot comments on these are almost always false positives:
.md,.json,.yaml,.ymlconfig files (unless security-related).lockfiles (auto-generated).sqlmigration files (auto-generated)- Files with "auto-generated" or "DO NOT EDIT" headers
Convention contradictions
If a bot finding contradicts a rule in the project's CLAUDE.md or AGENTS.md, mark as ignore-contradicts-conventions.
Outdated threads
Threads where isOutdated == true and the flagged code no longer exists. Verify by reading the current file; if the code has changed substantially, mark as ignore-outdated.
CI/CD Platforms
Use gh CLI for GitHub (PRs, Actions, checks). Use platform-native CLIs for Buildkite, Vercel, and Fly.io.
Contents
Universal status check
All CI/CD platforms register as GitHub checks. One command covers status:
gh pr checks --json name,state,conclusion,detailsUrlWatch all checks until they complete:
gh pr checks --watchWait for only required checks:
gh pr checks --watch --requiredIdentify the platform from the check name:
| Pattern | Platform |
|---|---|
buildkite/ prefix | Buildkite |
Vercel, vercel in name, or vercel.com in detailsUrl | Vercel |
fly-deploy, fly- prefix, or fly.io in detailsUrl | Fly.io |
| Everything else | GitHub Actions (default) |
GitHub Actions
List recent runs for the branch:
gh run list --branch {branch} --limit 5 --json databaseId,name,status,conclusionView a specific run:
gh run view {run_id}Fetch failed job logs (most useful for diagnosis):
gh run view {run_id} --log-failedFetch full logs:
gh run view {run_id} --logRe-run failed jobs only:
gh run rerun {run_id} --failedRe-run entire workflow:
gh run rerun {run_id}Watch a run until completion:
gh run watch {run_id}Cancel a run:
gh run cancel {run_id}Buildkite
Buildkite registers as GitHub checks with a buildkite/ prefix. Status is always available via gh pr checks. Logs and retries require authenticated access: use the fallback chain below.
Status (always works)
gh pr checks --json name,state,conclusion,detailsUrl | jq '.[] | select(.name | startswith("buildkite/"))'Check name format is typically buildkite/{org}/{pipeline}. The detailsUrl links directly to the Buildkite build page.
Auth Fallback Chain
Try these in order. Stop at the first one that works.
1. `bk` CLI (if authenticated)
Test auth first:
bk auth status 2>&1If this returns a 401, expired token, or any error, skip to option 2. Do not retry or prompt the user to re-authenticate during a monitor cycle.
If auth is valid:
bk build view --pipeline {pipeline} --branch {branch}
bk job log --pipeline {pipeline} --build {build_number} --job {job_id}
bk build retry --pipeline {pipeline} --number {build_number}2. Buildkite REST API (if `BUILDKITE_API_TOKEN` is set)
# List builds for the branch
curl -sH "Authorization: Bearer $BUILDKITE_API_TOKEN" \
"https://api.buildkite.com/v2/organizations/{org}/pipelines/{pipeline}/builds?branch={branch}&per_page=1"
# Get build details
curl -sH "Authorization: Bearer $BUILDKITE_API_TOKEN" \
"https://api.buildkite.com/v2/organizations/{org}/pipelines/{pipeline}/builds/{build_number}"
# Get job log
curl -sH "Authorization: Bearer $BUILDKITE_API_TOKEN" \
"https://api.buildkite.com/v2/organizations/{org}/pipelines/{pipeline}/builds/{build_number}/jobs/{job_id}/log"
# Retry a build
curl -sH "Authorization: Bearer $BUILDKITE_API_TOKEN" \
-X PUT "https://api.buildkite.com/v2/organizations/{org}/pipelines/{pipeline}/builds/{build_number}/rebuild"Extract org and pipeline from the check name: buildkite/{org}/{pipeline} maps to the API path organizations/{org}/pipelines/{pipeline}.
3. Fallback: `gh pr checks` + detailsUrl (always works)
If neither bk CLI nor BUILDKITE_API_TOKEN is available:
- Use
gh pr checksfor pass/fail status (always available) - Provide the
detailsUrllink for the user to check logs manually - Cannot retry builds without auth. Notify the user: "Buildkite build failed. Unable to fetch logs (no Buildkite auth). See: {detailsUrl}"
BK CLI Auth Recovery
If bk auth status fails and the user wants to fix it:
1. The bk CLI stores tokens in the macOS Keychain as bkua_* entries; these expire or get revoked periodically 2. Tell the user: "Run ! bk auth login to re-authenticate. I'll use the fallback until then." 3. Do not block the monitor cycle on auth recovery; continue with fallback options 4. On the next cycle, re-test bk auth status to pick up restored auth
Parsing detailsUrl
The detailsUrl from gh pr checks contains all the information needed:
https://buildkite.com/{org}/{pipeline}/builds/{build_number}Parse with:
org=$(echo "$url" | sed -E 's|https://buildkite.com/([^/]+)/.*|\1|')
pipeline=$(echo "$url" | sed -E 's|https://buildkite.com/[^/]+/([^/]+)/.*|\1|')
build_number=$(echo "$url" | sed -E 's|.*/builds/([0-9]+).*|\1|')Vercel
Vercel posts deployment status as GitHub checks. Use gh pr checks for status, vercel CLI for logs and inspection.
Check deployment status:
gh pr checks --json name,state,conclusion,detailsUrl | jq '.[] | select(.name | test("vercel|Vercel"; "i"))'Inspect a deployment:
vercel inspect {deployment_url}View build logs:
vercel logs {deployment_url}Stream live logs:
vercel logs {deployment_url} --followList recent deployments:
vercel ls --limit 5Force redeploy:
vercel --forceCommon Vercel failures:
- Build errors: read logs for compilation/bundling errors
- Environment variable missing: check
vercel env ls - Timeout: notify user (infrastructure issue)
Fly.io
Check status via GitHub checks (if configured):
gh pr checks --json name,state,conclusion,detailsUrl | jq '.[] | select(.name | test("fly"; "i"))'Check app status:
flyctl status --app {app_name}View logs:
flyctl logs --app {app_name} --no-tailStream live logs:
flyctl logs --app {app_name}View recent releases:
flyctl releases --app {app_name}Trigger deployment:
flyctl deploy --app {app_name}Check health:
flyctl checks list --app {app_name}Common Fly failures:
- Health check failure: check logs for crash/startup errors
- OOM: notify user (increase memory in
fly.toml) - Migration error: read logs, may need manual intervention
Discovering the app name:
1. Check fly.toml in the repo root for app = "name" 2. If not found, notify the user that the app name could not be determined
Failure classification
Decision tree for diagnosing CI/CD failures:
1. Error contains "flaky", "timeout", or matches known flaky test pattern → re-run the check 2. Type error referencing a sibling/workspace package's types, or "Cannot find module"/missing generated types → likely stale dependency, not a code bug. Reinstall and rebuild deps, regenerate types, then re-run type-check (see below). Only treat as a code error if it persists 3. Compilation/type/lint error in logs (not stale-dependency) → code fix needed. Read the error, fix the file, commit, push 4. `knip` failure (unused files, exports, or dependencies) → remove the dead export/file/dep. If the report is intentional (e.g. a public API entry point), add it to the knip config's ignore/entry instead. Commit, push 5. "rate limit", "quota", "infrastructure", "service unavailable" → notify user (not fixable from code) 6. "npm ERR!", "dependency", "resolution", "peer dep" → reinstall (delete lockfile + node_modules if needed), and in a monorepo rebuild dependency packages so workspace types resolve, commit, push 7. "OOM", "memory", "killed" → notify user (infrastructure; needs a config change) 8. Test assertion failure (not flaky) → read failing test and source, fix, commit, push 9. Unknown → fetch full logs, attempt diagnosis, notify user if unsure
When re-running checks, wait for the re-run to complete before diagnosing again. Do not re-diagnose while checks are pending.
Stale-dependency type-check failures
In a monorepo, a type-check often fails not because the changed code is wrong but because a dependency package's build output or generated types are out of date relative to the working tree. Symptoms: errors pointing into node_modules/dist of a sibling package, types that exist in source but not in the resolved declaration, or a green local editor but a red CI type-check.
Before editing source, refresh the dependency graph and re-run:
# reinstall (use the repo's package manager)
npm ci # or: yarn install --immutable / pnpm install --frozen-lockfile
# rebuild dependency packages so workspace types resolve (use the repo's task runner)
turbo run build --filter=...[changed] # or: nx affected -t build / make build-deps
# regenerate any codegen'd types (GraphQL, OpenAPI, etc.) the repo defines
# then re-run the type-checkIf the type-check passes after the refresh, it was a stale-dependency issue and no source change is needed. If it still fails, treat it as a real code error (item 3).
Fix Plan Template
Write the fix plan to .claude/scratchpad/pr-{N}-review-plan.md. Create the .claude/scratchpad/ directory if it does not exist.
The plan is an audit trail: triage proceeds without waiting for approval. If the user edits the file mid-run, re-read it before executing the Fix step and respect their edits.
Template
# PR #{N} Review Comment Plan
**PR:** {title} (#{N})
**Branch:** {branch}
**URL:** {pr_url}
**Threads:** {total} total, {unresolved} unresolved, {outdated} outdated
**Reviews:** {review_count} ({changes_requested} requesting changes)
**Generated:** {date}
## Summary
| Disposition | Critical | Major | Minor | Nitpick | Total |
|-------------|----------|-------|-------|---------|-------|
| Fix | | | | | |
| Ignore | | | | | |
---
## Issues to Fix
Ordered by severity (critical first), grouped by file proximity.
### 1. [{severity}] {short title}
- **Thread:** {thread_node_id}
- **File:** `{path}:{line}`
- **Author:** @{author} ({human | bot_name})
- **Category:** {bug | security | performance | style | correctness | docs | test-coverage}
- **Finding:** {one-sentence description}
- **Fix approach:** {concrete description of what to change}
- **Commit group:** {group_label}
> Original: {relevant excerpt from comment, boilerplate stripped}
---
### 2. ...
---
## Conversation Items (no thread, reply only)
Items from issue-level comments or review bodies. These cannot be resolved via
GraphQL; reply to acknowledge, but there is no resolve action.
### C1. [{severity}] {short title}
- **Source:** {issue comment | review body (CHANGES_REQUESTED)}
- **Comment ID:** {comment_id or review_id}
- **Author:** @{author}
- **Finding:** {one-sentence description}
- **Fix approach:** {concrete description of what to change}
- **Reply to post:** "{acknowledgment message}"
- **Commit group:** {group_label}
> Original: {relevant excerpt}
### C2. ...
---
## Ignored
### I1. [{reason}] @{author} on `{path}:{line}`
- **Thread:** {thread_node_id}
- **Reason:** {specific explanation}
- **Reply to post:** "{brief resolution comment}"
### I2. ...Template notes
- Replace all
{placeholders}with actual values - Thread IDs are GraphQL node IDs (used for resolve mutations in the triage Fix step)
- Comment IDs are REST
idordatabaseIdfields (used for reply endpoints) - Commit group labels batch related fixes into single commits (e.g., "golden-events", "lint-cleanup")
- Keep resolution reply comments to one sentence
- The summary table gives the user a quick overview before reading details
- If the user moves items between Fix/Conversation/Ignore sections, respect their edits
- Conversation items that are purely informational (soft suggestions with "up to you") may be moved to Ignored by the user
Git Resilience
How to recover when a git command hangs or fails transiently inside the poll cycle. A hung git call should retry, not abort the monitor.
Contents
core.fsmonitor hangs
Symptom: git status, git fetch, git rebase, or git commit stalls indefinitely with no output. Common in large monorepos where the filesystem-monitor daemon wedges.
Diagnosis:
git config --get core.fsmonitor # true / a hook path = fsmonitor is activeRecovery: disable it for the session and retry the command:
git config core.fsmonitor false
# kill any wedged daemon, then retry
pkill -f 'fsmonitor--daemon' 2>/dev/null || trueFor read-only status calls that only need to observe state, skip lock acquisition entirely:
GIT_OPTIONAL_LOCKS=0 git status --porcelainStale index.lock contention
Symptom: fatal: Unable to create '.../.git/index.lock': File exists.
Recovery: remove the lock only when no git process is running; deleting it under a live process corrupts the index.
pgrep -f '[g]it ' && echo "git running: wait, do not delete lock" || rm -f .git/index.lockThen retry the original command.
Transient IPC hiccups
Brief, self-clearing failures (an intermittent gh/git IPC error, a momentary network blip on git fetch). Retry with backoff rather than treating the first failure as terminal:
for attempt in 1 2 3; do
git fetch origin "$base_branch" && break
sleep $((attempt * 2))
doneSafe-retry posture
- Treat a hang or transient git error as retryable: apply the fsmonitor/lock recovery above, then retry once or twice with backoff.
- Only abort the current phase (and notify the user) if the command still fails after recovery + retries; never let a single transient git failure kill the monitor.
- Keep recovery changes session-local (
git config core.fsmonitor falseaffects the local repo config only); don't push config changes as part of a PR.
GitHub API Reference
Queries and mutations for fetching, replying to, and resolving PR review threads, comments, and reviews.
Contents
- Extract owner, repo, and PR number
- Fetch review threads (GraphQL)
- Fetch PR reviews (REST)
- Fetch issue-level comments (REST)
- Reply to a thread
- Reply to an issue-level comment
- Resolve a thread
- Pagination pattern
Extract owner, repo, and PR number
Auto-detect from the current branch:
gh pr view --json number,url,title,headRefName,baseRefNameGet owner and repo from the current repository:
gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"'If the user provides a PR number, use it directly. Otherwise parse number from the gh pr view output.
Fetch review threads (GraphQL)
This is the only reliable source of thread resolution status. REST endpoints do not expose isResolved.
query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
isResolved
isOutdated
path
line
comments(first: 20) {
nodes {
databaseId
author { login }
body
path
line
originalLine
createdAt
url
}
}
}
}
}
}
}Invoke with gh api graphql:
gh api graphql \
-f query='...' \
-f owner="$OWNER" \
-f repo="$REPO" \
-F pr="$PR_NUMBER"Post-fetch filtering:
- Keep threads where
isResolved == false - Note
isOutdatedthreads: the diff may have moved; flag for extra scrutiny - Threads with
path: nullare PR-level comments (not inline)
Fetch PR reviews (REST)
Reviews contain the reviewer's overall verdict and may include actionable body text (especially CHANGES_REQUESTED reviews).
gh api --paginate "repos/{owner}/{repo}/pulls/{pr}/reviews"Each review has:
state:APPROVED,CHANGES_REQUESTED,COMMENTED,DISMISSEDbody: review-level comment (may be empty when the reviewer put content in inline comments instead)user.login: reviewer username
Triage rules:
CHANGES_REQUESTEDwith non-empty body → actionable, classify the body textCHANGES_REQUESTEDwith empty body → the reviewer's inline comments carry the requestAPPROVEDwith empty body → skip (just an approval)COMMENTEDfrom a bot → check if the body contains findings (Devin, etc.)COMMENTEDfrom a human + immediateAPPROVED→ non-blocking conversational question
To get inline comments from a specific review:
gh api "repos/{owner}/{repo}/pulls/{pr}/reviews/{review_id}/comments"Fetch issue-level comments (REST)
Top-level PR conversation comments (not inline review threads):
gh api --paginate "repos/{owner}/{repo}/issues/{pr}/comments?per_page=100"These cannot be "resolved" via the thread mechanism. Include in triage but handle differently: they need a reply, not a resolve mutation.
Do not filter by author type. Both human and bot issue-level comments may be actionable:
github-actions[bot]posts DangerJS warnings and schema compatibility checks here- Human reviewers post suggestions and questions here
linear[bot]posts linkbacks here (noise; classify by content)
Classify each comment by its content using the rules in bot-patterns.md.
Reply to a thread
Use the REST reply endpoint (most reliable):
gh api "repos/{owner}/{repo}/pulls/{pr}/comments/{comment_database_id}/replies" \
-X POST \
-f body="Done: fixed in latest push."The comment_database_id is the databaseId of the last comment in the thread (reply to the most recent message).
GraphQL alternative (use if REST fails):
mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: $threadId
body: $body
}) {
comment { id }
}
}Reply to an issue-level comment
Issue-level comments use a different endpoint. There is no thread mechanism; just post a new comment on the PR:
gh api "repos/{owner}/{repo}/issues/{pr}/comments" \
-X POST \
-f body="Acknowledged: addressed in latest push."To reply to a specific comment contextually, quote the original in your reply body.
Resolve a thread
mutation($threadId: ID!) {
resolveReviewThread(input: { threadId: $threadId }) {
thread { isResolved }
}
}Invoke:
gh api graphql \
-f query='mutation($threadId: ID!) { resolveReviewThread(input: { threadId: $threadId }) { thread { isResolved } } }' \
-f threadId="$THREAD_ID"Always post a reply before resolving so the reviewer sees the resolution reason.
Issue-level comments and review bodies cannot be resolved; they have no thread mechanism. Reply to acknowledge, but there is no "resolve" action.
Pagination pattern
The API returns max 100 threads per page. Paginate until hasNextPage is false:
cursor=""
all_threads="[]"
while true; do
if [ -z "$cursor" ]; then
result=$(gh api graphql -f query='...' -f owner="$OWNER" -f repo="$REPO" -F pr="$PR")
else
result=$(gh api graphql -f query='...' -f owner="$OWNER" -f repo="$REPO" -F pr="$PR" -f cursor="$cursor")
fi
page=$(echo "$result" | jq '.data.repository.pullRequest.reviewThreads.nodes')
all_threads=$(echo "$all_threads $page" | jq -s 'add')
has_next=$(echo "$result" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
[ "$has_next" = "true" ] || break
cursor=$(echo "$result" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
doneMost PRs have fewer than 100 threads, so a single page suffices. Always check hasNextPage regardless.
Merge Conflicts
Detection, resolution strategies, and safety guardrails for keeping a PR branch up to date.
Contents
- Conflict Detection
- Resolution Strategy
- Rebase Workflow
- Auto-Resolvable Conflicts
- Do NOT Auto-Resolve
- Safety Guardrails
Conflict Detection
gh pr view --json mergeable,mergeStateStatusmergeStateStatus | Meaning | Action |
|---|---|---|
CLEAN | No conflicts, up to date | Skip |
BEHIND | Base has advanced, no conflicts yet | Rebase to update |
DIRTY | Actual conflicts exist | Resolve |
UNSTABLE | Mergeable but failing checks | Skip (Phase 3 handles checks) |
BLOCKED | Branch protection prevents merge | Skip (review/checks needed) |
HAS_HOOKS | Merge hooks pending | Skip |
UNKNOWN | GitHub is computing | Wait, recheck next cycle |
Resolution Strategy
Default to rebase. Use merge instead only when the branch is shared: rebase rewrites commits other contributors have based work on.
Detect a shared branch without asking the user:
git log origin/{base_branch}..HEAD --format='%ae' | sort -uMore than one author email means the branch is shared; merge origin/{base_branch} instead of rebasing. A user who explicitly prefers merge overrides the default.
Rebase Workflow
# 1. Stash any uncommitted work
git stash
# 2. Fetch latest base
git fetch origin {base_branch}
# 3. Attempt rebase
git rebase origin/{base_branch}
# 4a. If clean: push
git push --force-with-lease
# 4b. If conflicts: evaluate (see below)
# On abort:
git rebase --abort
git stash popAuto-Resolvable Conflicts
These file types can be resolved automatically:
Lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml): 1. Abort the rebase 2. Merge base branch instead: git merge origin/{base_branch} 3. Accept the incoming lockfile 4. Re-run the package manager: npm install / yarn / pnpm install 5. Commit the regenerated lockfile 6. Push with --force-with-lease
Generated files (.generated, schema.graphql, GraphQL types): 1. Accept either side 2. Re-run the generation command 3. Commit the regenerated output
Changelogs (CHANGELOG.md, CHANGES.md): 1. Accept both sides 2. Reorder entries by date (newest first) 3. Commit
Config files with additive changes (both sides added different keys): 1. Accept both additions 2. Verify no semantic conflicts 3. Commit
Do NOT Auto-Resolve
Notify the user and provide details for these:
- Source code with logic conflicts: both sides modified the same function body
- Database migrations: ordering matters; incorrect resolution breaks the migration chain
- API contracts / OpenAPI specs: semantic changes require human judgment
- Files where both sides deleted and added on overlapping lines: intent is ambiguous
- Test files with conflicting assertions: the correct assertion depends on intent
When aborting, provide the user with:
- List of conflicting files
- Which lines conflict in each file
- What each side changed (ours vs theirs)
Safety Guardrails
1. Always `--force-with-lease`, never `--force`: the lease check ensures no one else pushed since your last fetch. If the lease fails, someone else has pushed; abort and notify 2. Stash before rebase: git stash saves uncommitted work. Pop after resolution 3. Abort on failure: if auto-resolution fails or produces unexpected results, always git rebase --abort to restore the branch 4. Never rebase a shared branch: if other contributors have pushed to this branch (multiple author emails in the detection command above), rebase rewrites their history; merge instead 5. Verify after resolution: after pushing, check that gh pr view --json mergeable returns MERGEABLE 6. One resolution per cycle: if a conflict reappears on the next poll (base advanced again), resolve again. Do not batch multiple base updates
Monitoring Setup
CronCreate configuration, state file format, and poll lifecycle for monitor mode.
Contents
- Schedule Patterns
- CronCreate Prompt Template
- State File Format
- Auto-Detection Defaults
- Stopping
- Session Lifecycle
Schedule Patterns
| User intent | Cron expression | Notes |
|---|---|---|
| Every 2 minutes (default) | */2 * * * * | Default: responsive polling for active PRs |
| Every 5 minutes | */5 * * * * | Lower API usage for stable PRs |
| Every 10 minutes | */10 * * * * | Minimal polling |
| Every 15 minutes | */15 * * * * | Background monitoring |
| Every hour | 7 * * * * | Use off-minute (:07) to avoid jitter on :00 |
Prefer off-minute scheduling: CronCreate adds jitter to tasks at :00 and :30. Pick a minute like 3, 7, or 13 for hourly+ intervals.
Recurring tasks auto-expire after 3 days. If the PR is still open, re-run /pr-babysitter to restart.
CronCreate Prompt Template
Check PR #{N} in {owner}/{repo}. Run pr-babysitter monitor phases 2-5:
1. Check for merge conflicts (gh pr view --json mergeable) and resolve if possible
2. Check CI/CD status (gh pr checks) and diagnose any failures. Use Buildkite auth fallback chain if needed.
3. Check for new review comments and triage autonomously if needed (no plan approval; fix and resolve directly)
4. Evaluate merge readiness and notify me of any state changes
State file: .claude/scratchpad/babysit-pr-{N}.md
Auto-resolve noise: yes
Auto-merge: noState File Format
Write to .claude/scratchpad/babysit-pr-{N}.md. Create directory if needed.
# Babysit PR #{N}
**PR:** {title} (#{N})
**URL:** {pr_url}
**Branch:** {head_branch} → {base_branch}
**Cron Job ID:** {job_id}
**Started:** {timestamp}
**Last Poll:** {timestamp}
## Preferences
- Auto-resolve noise: yes
- Auto-merge when ready: no
- Poll interval: every 2 minutes
## Current State
- **HEAD:** {sha}
- **Mergeable:** {MERGEABLE|CONFLICTING|UNKNOWN}
- **Review Decision:** {APPROVED|CHANGES_REQUESTED|REVIEW_REQUIRED}
- **Unresolved Threads:** {count}
- **Checks:**
- {check_name}: {SUCCESS|FAILURE|PENDING} ({platform})
- ...
## History
| Time | Event |
|------|-------|
| {timestamp} | {state change description} |
| ... | ... |Keep the history log to the last 20 entries. Older entries can be dropped.
Auto-Detection Defaults
No setup questions. The monitor auto-detects and applies sensible defaults:
| Setting | Default | Override |
|---|---|---|
| PR | Auto-detect from current branch | Pass PR number as argument |
| Poll interval | Every 2 minutes (*/2 * * * *) | "Poll every 5 minutes" |
| Auto-resolve noise | Yes | "Don't auto-resolve noise" |
| Auto-merge | No | "Enable auto-merge" |
| CI platforms | Auto-detected from gh pr checks | n/a (always auto-detected) |
Overrides can be given inline when invoking: "babysit PR #42, poll every 5 minutes, enable auto-merge."
Stopping
To stop monitoring:
1. Read the cron job ID from the state file 2. Call CronDelete with that job ID 3. Report final summary:
- Total polls run
- Conflicts resolved
- CI failures fixed
- Comments triaged
- Current PR state
Session Lifecycle
- Cron jobs are session-scoped: they stop when the Claude session ends
- 3-day auto-expiry on recurring jobs
- No persistence across session restarts
- If the session is busy when a poll is due, it fires when Claude becomes idle
Verification Gate
The checks that must pass before any commit the monitor pushes, plus the stray-artifact sweep that runs before commit. Replaces the soft "run lint/test if available": pushing red work or stray files wastes a whole poll cycle.
Contents
- When the gate runs
- Detect available checks
- Run order and scope
- Stray-artifact sweep
- Pre-commit hooks that emit artifacts
- Gate failure handling
When the gate runs
Run the gate after applying a fix (CI fix in Phase 3, or a review-comment fix in triage) and before the commit/push for that fix. If the gate fails, fix and re-run; do not push until it is green. This is local verification; CI is the backstop, not the first line of defence.
Detect available checks
Read the project's task runner and run only the checks that exist. Do not assume a fixed set of script names.
# npm/yarn/pnpm projects: read the scripts block
cat package.json | jq -r '.scripts | keys[]' 2>/dev/nullMap common names (a project may use any subset):
| Check | Common script names |
|---|---|
| lint | lint, lint:fix, eslint, oxlint |
| type-check | typecheck, type-check, tsc, check |
| test | test, test:unit, vitest, jest |
| dead code | knip |
Also handle non-npm runners: turbo run <task>, nx run <task>, or make <target>. If none of the checks exist, say so and skip the gate rather than inventing commands.
Run order and scope
Run in increasing cost order; stop and fix on the first failure.
1. type-check: fastest signal on a fix. Scope to changed files only where the tooling supports it; otherwise run the project script. 2. lint: scope to changed files (eslint <files>, oxlint <files>) when possible. 3. test: run the project test script. Scope to affected tests where a watch/affected mode exists; otherwise run the full suite. 4. knip: run last (project-wide by design). See the knip entry in ci-platforms.md for handling failures.
All present checks must pass before committing. A type-check failure may be a stale-dependency issue, not a code bug; check the stale-dependency branch in ci-platforms.md before treating it as a code error.
Stray-artifact sweep
Pre-commit hooks and build/check steps can dirty the working tree with files that are not part of the intended fix. The canonical case is a root-level schema.gql (or similar generated output) emitted by a hook. Committing these pollutes the PR and trips reviewers.
After running checks and hooks, inspect the tree:
git status --porcelainFor each untracked or modified file, decide:
- Intended: the file is part of the fix, or a tracked generated file the change is supposed to update. Keep it.
- Stray: a generated artifact the fix did not intend to touch (root
schema.gql, stray build output, an unrelated file a broad formatter rewrote). Revert or remove it before staging:
git restore <stray-tracked-file> # revert an unintended modification
rm <stray-untracked-file> # remove an unintended new file (e.g. root schema.gql)Stage only the files belonging to the fix: prefer git add <paths> over git add -A so stray files never get committed in the first place.
Pre-commit hooks that emit artifacts
A hook can dirty the tree during the commit, after your sweep. After committing, re-check:
git status --porcelainIf the commit left new stray files, strip them and amend (only safe pre-push, on the monitor's own commit):
git restore --staged . && rm <stray-files>
git commit --amend --no-editNever amend a commit that has already been pushed and may be on a teammate's machine.
Gate failure handling
- Lint/type/test failure on the fix: read the error, fix, re-run the gate. Do not push.
- Failure unrelated to the fix (a flaky test, a pre-existing type error elsewhere): note it; do not expand scope to fix unrelated breakage inside a comment-triage commit. If it blocks the gate, surface it to the user rather than silently pushing past it.
- Gate can't run (no scripts, missing deps): say so in the notification; rely on CI and flag that local verification was unavailable.