Agent skill

Babysit PR

by owid in owid/etl

Spawn a background agent that babysits an open PR — triggers a Codex review, watches CI, judges and fixes valid findings, replies to and resolves review threads, and loops to a cap.

MITAuto-check passedData & Analytics

Install Babysit PR

skills CLI
$ npx skills add owid/etl --skill babysit-pr -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install owid/etl babysit-pr --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/babysit-pr .claude/skills/babysit-pr && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
babysit-pr
GitHub stars
158
Token cost
~8.9k tokens
SKILL.md length
4,118 words
Files
1
Skills in repo
35
Repo updated
First seen
Licence
MIT

At a glance

Spawn a background agent that babysits an open PR — triggers a Codex review, watches CI, judges and fixes valid findings, replies to and resolves review threads, and loops to a cap.

  • Works in 4 steps: Post the trigger, capture its timestamp… → Judge each finding. Valid: real bugs,… → Fix valid findings: git pull --rebase… → …
  • Judges and fixes valid findings
  • SKILL.md covers When to use, Setup (main session) and Lessons already folded in (do…
  • Calls gh, git and jq

What it does

Babysit PR is an agent skill from owid/etl. Spawn a background agent that babysits an open PR — triggers a Codex review, watches CI, judges and fixes valid findings, replies to and resolves review threads, and loops to a cap. Never merges. Trigger when the user asks to "babysit the PR", "ask for codex review(s) and watch CI", or after pushing a substantial chunk to a PR branch.

Its SKILL.md is about 8.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Data & Analytics. The repository describes itself as: A compute graph for loading and transforming OWID's data. The licence is MIT.

When your agent uses it

  • Judges and fixes valid findings
  • Replies to and resolves review threads
  • The user asks to babysit the PR
  • Ask for codex review(s) and watch CI

Example prompts

  • “babysit the PR”
  • “ask for codex review(s) and watch CI”
  • “/babysit-pr”

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Post the trigger, capture its timestamp from the same call, and record the head SHA the round covers — the PR's remote head, which is the…
  2. Judge each finding. Valid: real bugs, wrong data handling, broken asserts, metadata errors. Invalid: style nitpicks contradicting…
  3. Fix valid findings: git pull --rebase FIRST (the branch may have moved; if the shared tree is dirty with the main session's work, use…
  4. Reply to every finding's inline comment (fixed → what you did + commit hash; rebutted → why). Every reply MUST start with this exact first…

What it can do on your machine

Read from SKILL.md and the folder at commit bf5dc8e. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • gh
    • git
    • jq
    • cursor
    • make

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use gh and git, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Babysit PR loads about 8.9k tokens when it runs. Until then it costs about 87 tokens; SKILL.md has 4,118 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~87
When it runs · the whole SKILL.md, loaded when a task matches
~8.9k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from owid/etl at commit bf5dc8e, republished under its MIT licence (© owid). 4,118 words, ~8,928 tokens.

Download SKILL.mdSave it as .claude/skills/babysit-pr/SKILL.md (or your agent's skills folder).
name
babysit-pr
description
Spawn a background agent that babysits an open PR — triggers a Codex review, watches CI, judges and fixes valid findings, replies to and resolves review threads, and loops to a cap. Never merges. Trigger when the user asks to "babysit the PR", "ask for codex review(s) and watch CI", or after pushing a substantial chunk to a PR branch.
metadata.internal
true
metadata.owner
paarriagadap

PR Babysitter

Run the full review → wait → fix → re-review loop for a PR hands-off, in a background agent, so the main session can keep working.

When to use

  • The user asks to get a PR reviewed and watched until green.
  • You just pushed a substantial chunk of work to a PR branch.

Only ONE babysitter per PR. If one is already running, message it (SendMessage) instead of spawning another, and never duplicate its work from the main session: don't post extra @codex review triggers, don't run your own CI watchers, don't reply to or resolve review threads yourself. If you must act on the PR (e.g. the user asks directly), tell the agent exactly what you did, with timestamps.

Setup (main session)

  1. A round in flight must reach a verdict — or be explicitly written off — before you start another one. Checking the surfaces once is not enough: a single empty read means Codex has not answered yet, and posting a new trigger on the strength of it moves the polling threshold so the answer, when it comes ~4–9 minutes later, is discarded exactly as if you had never checked. So:

    • Wait it out. Poll the current trigger's surfaces — an issues/<n>/comments clean pass ("Didn't find any major issues"), a +1 from the Codex bot on the trigger comment or the PR body, a new review with findings — until one of them fires. Then handle it, and only then trigger the next round. That handling is what makes the round harvested; reading a verdict and leaving its findings unaddressed is the same failure with extra steps.
    • Or write the round off — knowing you may not be able to tell its answer from the next one's. Abandoning a round does not cancel the job: it can answer after your new trigger. Step 2's Reviewed commit: rule catches the common case (the abandoned round reviewed different code, so its SHA identifies it as stale), but it cannot catch every case: if you re-trigger without pushing anything, both rounds review the same SHA and no signal distinguishes them. That is tolerable — two verdicts on identical code say the same thing about it — but it means a write-off is a bet, not a clean cut. Prefer pushing the fix first, so the rounds differ by SHA. Either way record, in the report, which SHA the abandoned round covered and that its verdict went unread: an abandoned round is a review nobody read, never a silent gap.

    Never a third option. "I checked and there was nothing, so I re-triggered" is the starvation bug in the lesson below.

0b. Don't trigger into a round that is already running. A PR becoming reviewable starts a Codex round by itself — both opening one non-draft and marking a draft ready are triggers, neither of them yours — and a manual trigger posted minutes later leaves two rounds in flight whose verdicts nothing can tell apart, because a +1 names no commit. You never need to work out which trigger fired, only whether something recent could have. Take the later of the PR's creation and its most recent readiness event:

bash
gh api repos/<owner>/<repo>/pulls/<n> --jq .created_at
gh api repos/<owner>/<repo>/issues/<n>/timeline --paginate --slurp \
  | jq -r '[.[][] | select(.event=="ready_for_review") | .created_at] | .[-1] // empty'

If the later of the two is within the last ~10 minutes, wait for that round instead of triggering — step 0's rules apply to it exactly as to one you posted, and an auto-round answers in about two to three minutes. Otherwise trigger.

Deliberately crude, and keep it that way: it never reasons about the PR's draft state, so there is no opening-versus-current-state trap to get wrong, and its only failure mode is waiting a few minutes on a PR whose round never existed. Earlier versions of this check tried to infer whether a round could have fired, from readiness events and the draft flag and draft conversions, and were wrong three times in a row — each fix opening the next hole. Recency subsumes all of it: whatever a PR's draft history, if it became reviewable long enough ago, any round it started has already answered. A Codex 👀 on the PR body or the trigger comment confirms a round is live right now; its absence proves nothing, since it is withdrawn as the verdict lands.

Note the shape of the timeline call, and keep it: --paginate applies --jq once per page, so an inline --jq prints one line per page and a caller reading the first or last line silently gets the wrong answer once a timeline exceeds one page. --slurp collects the pages into a single outer array, but gh rejects --slurp alongside --jq — hence the pipe to an external jq, and .[][] to flatten pages before selecting. The same trap applies to every --paginate call whose filter aggregates across pages.

Pass that timestamp to the agent whichever way the check comes out, so it can tell an auto-round's verdict from your own.

Waiting for an auto-round means adapting the template, which assumes a trigger you posted. Its opening line asserts an @codex review comment and step 2 reads the clean-pass +1 from that comment's id — neither exists for an automatic round, and an agent handed the template unchanged will either invent a comment id or skip a surface it was told was required. So when you hand off an auto-round, say so explicitly in the prompt: the polling threshold is the timestamp the PR became reviewable instead of a trigger timestamp, there is no trigger comment, so the trigger-comment reaction check is dropped and the +1 is watched on the PR body only (issues/<n>/reactions) alongside the three review surfaces. Do not hand it a head SHA for an auto-round. The round covers whatever the head was when the PR became reviewable, and that revision cannot be read back: the readiness event carries no SHA, and .head.sha returns the head now — a different commit whenever anything was pushed between readiness and the hand-off. So a written verdict's own Reviewed commit: line is the only coverage statement available, and a bare +1, which carries no such line, must be reported as covering an unknown revision rather than the current head. Tell it not to re-trigger on that round's verdict without your go-ahead — posting @codex review converts the auto-round into the ambiguous two-round case this step exists to avoid.

  1. Post the trigger, capture its timestamp from the same call, and record the head SHA the round covers — the PR's remote head, which is the revision GitHub hands Codex. Read it from the PR, never from local HEAD: a local branch that is ahead (an uncommitted-then-committed fix not yet pushed) or behind records a revision Codex never sees, which makes every verdict look unattributable. The creation response carries created_at, so there is no lookup to get wrong, but no SHA — and the recorded SHA is the round context step 2 compares Codex's own Reviewed commit: line against. Pass all three to the agent:

    bash
    gh api repos/<owner>/<repo>/issues/<n>/comments --method POST \
      -f body="@codex review" --jq '{id, created_at}'
    gh api repos/<owner>/<repo>/pulls/<n> --jq .head.sha   # AFTER the trigger: the remote head
    ```                                                    # Codex will actually pick up
    
    Keep **all three**: the timestamp is the polling threshold, the id is what step 2 reads reactions from (`gh api repos/<owner>/<repo>/issues/comments/<id>/reactions`) to see the `+1` clean signal, and the SHA is what any verdict from this round actually covers. Pass all three to the agent. Both *written* verdicts name the commit they examined — a findings review in its body, a clean pass in its issue comment — so the recorded SHA is what step 2 matches them against; only the bare `+1` reaction names nothing. That does not make it worthless — a Codex-authored `+1` is conclusive on its own when no other round could be answering (it was the only verdict PR 6590 ever produced) — but it is unattributable, so it cannot carry a round whose answer might be confused with another's. Same condition as step 2's rule; don't read it as stricter here, or a `+1`-only round waits to the deadline for a verdict that already arrived.
    
    Don't post with `gh pr comment` and then search for the comment: that lookup is a paginated connection, and on a PR with more than 30 issue comments the trigger you just posted is not on the first page.
  2. Spawn a general-purpose background agent with the prompt template below, filled in. The agent works in the SAME checkout on the SAME branch — warn it that the main session may also push commits mid-loop. Cleaner when available: give it a dedicated worktree of the branch, which sidesteps the shared-dirty-tree hazards in the lessons below — but note that doing so pins YOU to that worktree for the run: worktree isolation is keyed per session, not per agent, so entering a different one mid-loop refuses every command the agent issues (see the lessons below). 2b. Pace your own pushes. One trigger per commit starves the loop: Codex takes ~4–9 minutes to answer, so a push every few minutes means every round is superseded before it reports, and nobody reads the verdicts. Measured on a real PR: eight commits and eight triggers in ~40 minutes produced two clean passes and four findings reviews that the loop never harvested — a human noticed the unread "Didn't find any major issues" comment. When the user is iterating rapidly, batch the edits into one round; if you cannot, step 0's write-off rule applies — an abandoned round gets named in the report, with its SHA, as a review nobody read.

  3. When the completion notification arrives, relay the report. If the agent stops early (its notification says it is "waiting", "the monitor will notify me", "the waiter is still looping", or anything short of a final report), resume it with a message telling it to keep polling in short bash calls rather than ending its turn. Expect to do this 1–2 times per run — agents routinely stop early despite the prompt's warnings, so treat every non-final-report notification as a stall and send the corrective immediately (state the current trigger timestamp and any commits the main session pushed meanwhile).

Agent prompt template

Fill every <placeholder>. Keep all rules — each one exists because its absence caused a real failure.


You are babysitting PR #<n> on <repo> (branch <branch>) until CI is green and the Codex review is addressed. Work from <repo path>, already checked out on <branch>. A "@codex review" comment was posted at <exact UTC timestamp>, and the branch head at that moment was <SHA> — that is the revision this round's verdict covers, whatever the head is by the time you report. The main session may push its own commits to this branch while you work.

Loop (max <3> iterations, then stop and report):

  1. CI: gh pr checks <n> --watch --interval 60 run in the FOREGROUND of your own turn (up to 30 min) — never as a background task you then "wait on"; a backgrounded watcher does not resume you. On failure: read logs (gh run view <id> --log-failed), diagnose, fix.

  2. Wait for the review: poll every 2-3 minutes for a Codex response LATER than the trigger timestamp above, checking ALL THREE surfaces Codex can respond on:

    • pulls/<n>/reviews — a formal review, submitted when Codex has findings.
    • pulls/<n>/comments — the inline review comments carrying the individual findings.
    • issues/<n>/comments — a plain issue comment (e.g. "Didn't find any major issues") posted when Codex has NONE. This is the clean-pass verdict and it is NOT a review; watching only the reviews endpoint strands the loop forever on a clean pass.

    Do NOT treat a reaction on the trigger comment as the review arriving, unless it is a +1 from the Codex bot. Codex adds an acknowledgment reaction (👀-style) within seconds of the trigger, to the @codex review comment and the PR body, then removes both when it submits the real review minutes later (~3–9 min observed). The ack is therefore a live-round indicator, not a durable one: its presence means a round is running right now, and its absence means nothing at all. Never read a verdict — or a missed trigger — into either state. Exiting the wait on any codex reaction declares the PR "clean" while a review with findings is still in flight — this silently skipped two P2 findings once. So: a new review (reviews count increased) is the findings signal; a reaction with content == "+1" is the ONLY clean-signal reaction — and it counts ONLY when the reaction's user.login is the Codex bot itself. Mind the API split when matching: REST (which the reaction endpoints use) exposes the bot as chatgpt-codex-connector[bot], while GraphQL strips the suffix to chatgpt-codex-connector — match with a prefix predicate (login starts with chatgpt-codex-connector), not an exact string from the other API. A human collaborator's 👍 is noise, not a verdict — timestamp alone cannot tell them apart, and the reviews-count cross-check does not catch this while the real review is still in flight (it hasn't increased the count yet). The +1 can land on the PR DESCRIPTION instead of the trigger comment (gh api repos/<owner>/<repo>/issues/<n>/reactions — the issue-body reactions), and by the time it lands the 👀 acknowledgment has been withdrawn from both places — so poll BOTH reaction locations, and count a body +1 only when it is stamped after the trigger timestamp AND reacted by the Codex bot. After any apparent "clean" verdict (from a +1 in either location or an issue comment), cross-check that the reviews count did not also increase before you conclude there are no findings.

    Read every verdict's Reviewed commit: line, and report coverage from it. Codex stamps the SHA it examined into both kinds of verdict — a findings review's body and a clean-pass issue comment both contain **Reviewed commit:** <sha> (abbreviated, so compare by prefix). That SHA, not your recorded head, is what the verdict actually covers, and it is what the final report must state.

    Use it to reject exactly one thing: an answer you can positively identify as a previous round's. Keep the SHA recorded for every round in this run, and discard a verdict only when its reviewed SHA matches an earlier round's recorded SHA. Anything else — equal to this round's, newer than it, or a SHA no round recorded — is accepted as this round's verdict and reported under the SHA Codex named.

    Deliberately lopsided, because the failure modes are not symmetric. Attribution here is a heuristic, not an identity: the API exposes no round id, so

    • a push can race either side of the trigger, leaving the reviewed SHA newer or older than the one you recorded — rejecting on "not equal", or on ancestry alone, turns that race into a 30-minute hang;
    • two rounds triggered with no push between them share a SHA and are simply indistinguishable. That is survivable, because verdicts on identical code are interchangeable in substance; note both triggers in the report and move on.

    So a mismatch never stalls the loop. Accepting a stale answer costs a redundant round; hanging costs the whole run. A +1 reaction carries no SHA and so cannot be attributed at all; treat it as a clean signal only when no other round could be answering, and prefer the issue comment whenever one exists.

    One paginated GraphQL call covers both pulls/ surfaces with the fields you need to judge them — author and timestamp per finding, plus totalCount for the reviews-count cross-check. reviews(last: 20) returns the newest, so it needs no cursor of its own:

    bash
    gh api graphql --paginate -f query='
      query($endCursor: String) {
        repository(owner: "<owner>", name: "<repo>") {
          pullRequest(number: <n>) {
            reviews(last: 20) { totalCount nodes { databaseId author { login } submittedAt state body } }
            reviewThreads(first: 100, after: $endCursor) {
              pageInfo { hasNextPage endCursor }
              nodes { id isResolved comments(first: 1) { nodes { databaseId author { login } createdAt body } } }
            }
          }
        }
      }'

    databaseId on each review is what the final sweep reports as its coverage boundary, and body is where its Reviewed commit: line lives — drop either and the rule above cannot be applied (the inline finding comments do not carry the SHA, only the review that groups them). The bare thread query in step 6 is for resolving threads and carries neither author nor timestamp — polling with it cannot tell a new Codex response from a historical thread. issues/<n>/comments (the clean-pass verdict) and the trigger's reactions stay on REST; page the former with --paginate.

    Every one of these is a paginated connection — page all of them. Bare gh api .../pulls/<n>/reviews returns only the first 30 items, oldest first, so on a long-lived PR the newest review is not in the response and you will read a fresh review as silence (measured: page 1 fourteen hours stale, with 41 reviews and 60 inline comments present). GraphQL reviewThreads is the better route — it also hands you isResolved and the thread ids step 6 needs — but a bare first: N truncates exactly the same way, so page it too (query form in step 6). Either add --paginate to the REST calls or use the paginated GraphQL query; never a bare first page of anything.

    Poll in SHORT bash calls (one sleep 120 + all checks per call, repeated as separate tool calls) — never one long multi-minute loop, so that queued messages from the main session can reach you between calls. Give up after 30 minutes and say so in your report.

    Final sweep before the report: Codex can post an additional findings review minutes after the first, with no fresh trigger — a loop that concludes after handling the first response walks past the second. Immediately before writing the final report, re-check all three surfaces one last time, record in the report the latest review id + timestamp you saw, and state explicitly that anything Codex posts after that timestamp is outside your run's coverage. The sweep is a snapshot, not a quiet period — a second review has landed ~25 minutes after the first, so the late-review window is handed off to the main session as something nobody checked, never implicitly declared clean.

    The polling happens by YOU making the next tool call, in this same turn. There is no such thing as "arming a monitor", "watching for events", or waiting to be notified — nothing you set up keeps running once you stop, and phrases like "I'll act on events as they arrive" mean you have stopped. After every poll call that comes back empty, immediately make the next poll call. You end your turn exactly once: when the final report is written. Never before — not after CI passes, not after "setting up" anything, not while "waiting".

  3. Judge each finding. Valid: real bugs, wrong data handling, broken asserts, metadata errors. Invalid: style nitpicks contradicting CLAUDE.md conventions, or suggestions to undo deliberate decisions listed in the PR description. When a finding touches a decision you know the main session made deliberately, rebut rather than fix.

  4. Fix valid findings: git pull --rebase FIRST (the branch may have moved; if the shared tree is dirty with the main session's work, use --autostash). Use .venv/bin/ for everything. Verify with the relevant etlr steps and make check. Stage ONLY the files you edited (never git add -A) — and if the main session has uncommitted changes in a file you need to fix, do NOT commit that file at all: staging is file-level, so git add <file> would sweep those unreviewed hunks into your commit. Leave it uncommitted, flag the collision in your report, and let the main session fold the fix into its own commit. Commit 🐛🤖/🔨🤖 + "Co-Authored-By: Claude <model name> noreply@anthropic.com", push.

  5. Reply to every finding's inline comment (fixed → what you did + commit hash; rebutted → why). Every reply MUST start with this exact first line: > _Written by Claude <model name> — @<handle> at the wheel._ This is a public repo: plain language, no internal context, no names of people. 5b. If you touch the PR description, REWRITE it — never append. The body must describe the branch's current state, not how it got there. Fold each fix into the narrative where it belongs and delete whatever it supersedes; never add a "review follow-ups" or "second round" section, and never keep a per-fix changelog. Every finding's history already lives in the commit messages and the review threads you replied to, so repeating it in the body only buries the description a reviewer needs. Prefer leaving the body alone when a fix doesn't change what the PR does — a rollback-discipline fix usually belongs in the paragraph about the apply sequence, not in a new list.

  6. Resolve each thread you addressed (replying does not resolve it): match the REST inline-comment id to databaseId of the thread's first comment in GraphQL —

    bash
    gh api graphql --paginate -f query='
      query($endCursor: String) {
        repository(owner: "<owner>", name: "<repo>") {
          pullRequest(number: <n>) {
            reviewThreads(first: 100, after: $endCursor) {
              pageInfo { hasNextPage endCursor }
              nodes { id isResolved comments(first: 1) { nodes { databaseId } } }
            }
          }
        }
      }'

    --paginate needs both the $endCursor variable and the pageInfo block — without them it silently returns one page, which is the failure this whole rule exists to prevent. then gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "<id>"}) { thread { isResolved } } }'. Leave threads you did not address open for the human.

  7. Re-trigger a fresh bare @codex review comment ONLY if you pushed a substantial code fix (metadata-only tweaks don't count). Post it exactly as setup step 1 does, capturing all three fields:

    bash
    git push                                               # land the fix BEFORE triggering
    gh api repos/<owner>/<repo>/issues/<n>/comments --method POST \
      -f body="@codex review" --jq '{id, created_at}'
    gh api repos/<owner>/<repo>/pulls/<n> --jq .head.sha   # AFTER the trigger, as in step 1

    Replace all three stored values — the threshold, the trigger comment id, and the head SHA (again from the remote, after the push has landed: an unpushed fix commit yields a SHA Codex never sees). Keeping the old id means step 2 reads reactions from the previous trigger, where an existing +1 declares the new round clean before Codex has answered it. Keeping the old SHA is subtler and worse: the round's verdict then gets reported against the revision you just replaced, so a clean pass reads as covering code Codex never saw, and the revision it did see goes unnamed. Then loop back to 1.

  8. NEVER merge. Never force-push. Never edit dag/archive/*.

Final report: status of every CI check; each finding with verdict (fixed+commit / rebutted+why); threads resolved; commits pushed; the SHA each verdict covers — the SHA from that verdict's own Reviewed commit: line, with the round's recorded head as context when the two differ (and the head at report time if that differs too, since a clean pass on an older revision says nothing about newer commits); anything left for the human.


Show full SKILL.md (1,908 more words)Show less

Lessons already folded in (do not relearn)

  • The agent must keep polling within its turn (ending the turn "to wait" strands the loop until someone resumes it) — but in short bash calls, never one long multi-minute loop: messages from the main session can only be delivered between tool calls, so a long sleep makes the agent unreachable.
  • Agents talk themselves into stopping with "monitors are armed, I'll act on events as they arrive" — there are no monitors; nothing runs after the turn ends. The prompt must say explicitly that polling means making the next tool call yourself, and that the turn ends exactly once, at the final report.
  • The same stall wears other disguises: backgrounding gh pr checks --watch (or any "waiter") and ending the turn "until its notification arrives". A backgrounded watcher notifies no one who can act. Even with all warnings in the prompt, agents stall this way 1–2 times per run — the main session's SendMessage corrective ("poll yourself in short calls; end the turn only at the final report") reliably restarts them, so budget for it rather than treating it as exceptional.
  • Page every review surface, on both APIs. gh api repos/<o>/<r>/pulls/<n>/reviews (and /comments) returns only the FIRST PAGE — 30 items, oldest first — so on a long-lived PR the newest review is not in the response at all and the loop reads a fresh review as silence. Measured on a real PR: page 1's newest review was 14 hours stale (41 reviews and 60 inline comments existed; 30 of each were visible). Use the reviewThreads GraphQL query — which also gives you isResolved and the thread ids you need to resolve them — or pass --paginate. This cost ~25 minutes of a run: the review arrived in 4.5 minutes and the poller never saw it. The GraphQL route has the same trap — a bare reviewThreads(first: 50) truncates just as silently, and a heavily-reviewed PR reaches that (one hit 30 threads across five rounds) — so page the connection with $endCursor + pageInfo, not just the REST calls. Where a value can be read from a creation response instead of looked up — the trigger comment's created_at — do that: it is O(1) and structurally cannot go stale.
  • Codex answers on three surfaces: a formal review (pulls/<n>/reviews), the inline finding comments (pulls/<n>/comments), and — when there are no findings — a plain issue comment ("Didn't find any major issues", issues/<n>/comments). Poll all three, or a clean pass strands the loop until the deadline.
  • A reaction on the trigger comment is NOT the review, unless content == "+1" from the Codex bot. Codex adds a fast 👀-style acknowledgment reaction and submits the real review minutes later (~3–9 min), withdrawing the ack as it does — see the transient-acknowledgment lesson below. Exiting on any reaction declared "clean" and silently skipped two P2 findings (PR 6506). Use a review-count increase as the findings signal and a Codex-authored +1 as the only clean-signal reaction; after any clean verdict cross-check the reviews count didn't also increase.
  • The clean-pass +1 can land on the PR description instead of the trigger comment (PR 6589: the only verdict signal was a +1 on the issue body ~2m40s after a trigger — no issue comment, no review, and no reaction on the trigger comment itself). A loop watching only the trigger's reactions polls blind to its deadline; a human noticed the thumbs-up first. Poll the issue-body reactions too (issues/<n>/reactions), accept a +1 there only when stamped after the trigger timestamp AND user.login is the Codex bot, and keep the reviews-count cross-check.
  • A PR becoming reviewable starts its own Codex round, which answers within a few minutes with no trigger comment anywhere — both opening one non-draft and marking a draft ready do it. Trigger on top of that and a +1 (naming no commit, with the 👀 already withdrawn) belongs to neither round: the verdict is unattributable, and no timing arithmetic recovers which trigger earned it. Don't try to infer whether a round could have fired from draft states and timeline events — that reasoning was wrong three times running. Ask only whether the PR became reviewable recently, per step 0b, and wait if it did. It does not fire on every PR — it appears tied to the PR author's own Codex connection.
  • The 👀 acknowledgment is transient, on both the trigger comment and the PR body: present while a round is in flight, withdrawn as the verdict lands. So a present 👀 says a round started, and an absent one says nothing at all — never read a verdict, or a missed trigger, into either state. Only a Codex +1, an issue comment, or a new review is a verdict. (A post-hoc count of finished rounds finds zero reactions, which is what once made this look like "Codex never reacts".)
  • A manual @codex review is answered on a draft PR, so draft state is never the explanation for a missing verdict — don't let an agent report "no verdict, probably because it's a draft", and don't mark a PR ready to coax a review out of it, which just starts a second round.
  • A +1 verdict must be authored by the Codex bot, not just well-timed. Humans routinely 👍 a PR description; content + timestamp alone would read that as a clean pass, and the reviews-count cross-check can't save you while the real findings review is still in flight (it hasn't been submitted yet, so the count hasn't moved). Always check the reaction's user.login before treating any +1 as a verdict, in both reaction locations — and match it as a prefix (chatgpt-codex-connector), because REST renders the bot login as chatgpt-codex-connector[bot] while GraphQL renders it without the suffix; an exact-string check copied from the wrong API silently rejects every genuine verdict and stalls the loop to its deadline.
  • Replying to a review comment does NOT resolve the thread; resolution is a separate GraphQL mutation.
  • Every re-trigger resets the polling threshold; deleted trigger comments make timestamps lie — always pin the threshold to a comment that still exists.
  • The round's SHA is the PR's REMOTE head, not local HEAD. GitHub sends Codex the pushed revision, so a local branch that is ahead (a fix committed but not yet pushed) or behind records a SHA Codex will never name. Read it with gh api repos/<o>/<r>/pulls/<n> --jq .head.sha, after the push has landed and after posting the trigger.
  • The SHA labels verdicts; it must never gate them. A push can race either side of the trigger, so Codex may review a revision newer or older than the one recorded, and two rounds with no push between them share a SHA entirely. Attribution is therefore a heuristic — the API exposes no round id. Reject a verdict only when its reviewed SHA positively matches an earlier round's recorded SHA; accept everything else and report coverage from the Reviewed commit: line rather than from what you recorded. Any stricter rule (equality, or ancestry in one direction) converts a routine race into a 30-minute hang, and the trade is lopsided: a stale answer costs one redundant round, a hang costs the run.
  • Attribute verdicts by Reviewed commit:, never by timestamp alone. Codex stamps the SHA it examined into both verdict kinds — the findings review body and the clean-pass issue comment (**Reviewed commit:** 240588d338). Without matching it against the round's recorded head SHA, a late answer from an abandoned or superseded round passes the timestamp filter and is read as the current round's verdict; on a clean pass that silently certifies a revision Codex never saw. The +1 reaction is the one verdict with no SHA in it, which is why it cannot be the sole clean signal whenever another round might still answer.
  • Rapid re-triggering starves the loop — harvest before re-triggering. Every new @codex review resets the polling threshold, so any verdict that arrived for the previous trigger is discarded unread. On a PR taking eight commits and eight triggers in ~40 minutes, Codex answered every round (two clean passes, four findings reviews) and the loop harvested none of them, because each answer landed after its trigger had already been superseded; the unread clean pass was spotted by a human, not by the loop. Two guards, both in Setup above: step 0 — read every surface for the current trigger before posting a new one — and pacing — batch edits into one round instead of triggering per commit. Also state, in every relayed report, which SHA the verdict covers: with a moving head, "clean" without a SHA is unfalsifiable.
  • A concluded loop is not a closed review. Codex posted a second findings review ~25 minutes after the first, with no new trigger and no new push, after the babysitter had legitimately finished its round (PR 6561: a P1 sat unaddressed until a human noticed). Hence the final-sweep rule in step 2 — and when relaying a babysitter's report, treat its "latest review id seen" as the coverage boundary, not as proof the review is over.
  • Main session and agent share the checkout: both must git pull --rebase before committing, and the main session must not run a parallel review/CI loop. A dirty shared tree adds two hazards — plain git pull --rebase refuses to run (needs --autostash), and staging is file-level, so committing a file the main session also edited sweeps its unreviewed hunks into the agent's commit. Both guards are baked into step 4 of the prompt template — they must live INSIDE the template, since a rule listed only in this section never reaches the spawned agent. The clean way to sidestep both hazards is to hand the babysitter its own worktree of the branch.
  • Worktree isolation is keyed per SESSION, not per agent — so once a babysitter has its own worktree, the main session must stay put. Calling EnterWorktree on a different worktree re-keys the isolation both share, and from then on every Bash call the agent makes against its own tree is refused ("this command's working directory resolved to the shared checkout"), down to pwd. The agent cannot fix it from its side: EnterWorktree({path}) back to its own worktree reports success while the guard keeps reading the new one. Only the main session moving back clears it. So plan the run: if you need a second branch mid-loop (a follow-up PR, an unrelated fix), either wait for the babysitter to finish or accept that it is read-only until you return. Two behaviours make this survivable, and both belong in the agent's prompt — keep polling read-only rather than stopping (gh api calls don't touch the tree, so the loop stays alive and the round is not abandoned), and report blocked rather than editing from the wrong tree. Tell the agent explicitly when you're back, since it has no way to detect the guard clearing. (Cost one run several minutes: a follow-up PR opened mid-loop locked the babysitter out until the main session returned.)
  • A review loop that appends to the PR description buries it. Three rounds of fixes on PR 6600 grew a round-by-round log into 26 of the body's 58 lines, pushing what the PR actually does below the fold; the user asked for it to stop accumulating. The body is a description of the current branch, and the history is already carried twice over — in the commit messages and in the review threads. Hence step 5b: fold fixes into the narrative and delete what they supersede. The same rule governs the main session (memory: "Keep PR description in sync with each substantial change"), so agent and main session must not drift on it.

© owid, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/babysit-pr of owid/etl.

Open the folder on GitHubat commit bf5dc8e

Compare with similar skills

Babysit PR next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Babysit PR compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Babysit PR this skillowid/etl158—~8.9kAutomated safety check: PassMIT
Exploratory Data Analysisspacering-net/codeg3.8k15 repos~3.6kAutomated safety check: PassMIT
MatplotlibzLanqing/codex-claude-academic-skills4.6k18 repos~2.9kAutomated safety check: PassMIT
Scikit LearnzLanqing/codex-claude-academic-skills4.6k17 repos~3.9kAutomated safety check: PassBSD-3-Clause
Chart Visualizationbytedance/deer-flow83k2 repos~840Automated safety check: PassMIT
TimesFM Forecastinggoogle-research/timesfm34k—~4.7kAutomated safety check: PassApache-2.0

Similar skills

  • Exploratory Data Analysis

    spacering-net/codeg

    Perform comprehensive exploratory data analysis on scientific data files across 200+ file formats.

    3.8k GitHub starsUsed in 15 repos~3.6k tokens
    Data & AnalyticsAuto-check passed
  • Matplotlib

    zLanqing/codex-claude-academic-skills

    Low-level plotting library for full customization. An agent skill from zLanqing/codex-claude-academic-skills.

    4.6k GitHub starsUsed in 18 repos~2.9k tokens
    Data & AnalyticsAuto-check passed
  • Scikit Learn

    zLanqing/codex-claude-academic-skills

    Machine learning in Python with scikit-learn. An agent skill from zLanqing/codex-claude-academic-skills.

    4.6k GitHub starsUsed in 17 repos~3.9k tokens
    Data & AnalyticsAuto-check passed
  • Chart Visualization

    bytedance/deer-flow

    Picks a suitable chart type from 26 options for your data, maps the data to that chart's parameters and generates a chart image through a JavaScript script.

    83k GitHub starsUsed in 2 repos~840 tokens
    Data & AnalyticsAuto-check passed
  • TimesFM Forecasting

    google-research/timesfm

    Forecasts any univariate time series zero-shot with Google's TimesFM model, returning point forecasts and calibrated prediction intervals without training.

    34k GitHub stars~4.7k tokensUpdated 9 days ago
    Data & AnalyticsAuto-check passed
  • Sandbox Bench

    vercel/next.js

    Official

    Benchmark React or Next.js changes on Vercel Sandbox VMs with paired A/B statistics: react PR/commit vs base, or Next.js PR/commit vs base, measured end-to-end through the bench/render-pipeline app…

    143k GitHub stars~4.1k tokensUpdated today
    Data & AnalyticsAuto-check passed

More from owid/etl

All 35 skills in this repo
  • Find every OWID surface that references a chart, indicator, MDIM, or explorer — articles (links vs embeds), explorers, narrative charts, data insights, static viz, key-chart slots, MDIM views.

    158 GitHub stars~4.9k tokensUpdated today
    Auto-check passed
  • Add a scatter view (with GDP per capita on x) to existing OWID charts via the admin API, mirroring the admin UI's "Add scatter type" defaults, then retire the old standalone "X vs.

    158 GitHub stars~19k tokensUpdated today
    Auto-check passed
  • Add new survey question codes (e.g. An agent skill from owid/etl.

    158 GitHub stars~11k tokensUpdated today
    Auto-check: notes
  • Build or refresh an OWID static visualization end to end — resolve what data it needs from an old static viz image, an indicator, or a grapher chart; check both the ETL catalog and the producer's…

    158 GitHub stars~8.3k tokensUpdated today
    Auto-check passed
  • Propose redirects from (soon-to-sunset) grapher charts to the matching views of published MDIMs.

    158 GitHub stars~9.6k tokensUpdated today
    Auto-check: notes
  • Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end.

    158 GitHub stars~7.3k tokensUpdated today
    Auto-check: notes

Questions about Babysit PR

What does Babysit PR do?

Spawn a background agent that babysits an open PR — triggers a Codex review, watches CI, judges and fixes valid findings, replies to and resolves review threads, and loops to a cap. Babysit PR is an agent skill from owid/etl. Spawn a background agent that babysits an open PR — triggers a Codex review, watches CI, judges and fixes valid findings, replies to and resolves review threads, and loops to a cap.

When should I use Babysit PR?

Babysit PR fits situations like: judges and fixes valid findings; replies to and resolves review threads; the user asks to babysit the PR; ask for codex review(s) and watch CI.

How do I install Babysit PR in Claude Code?

Run `npx skills add owid/etl --skill babysit-pr -a claude-code`. Or copy the skill folder (.claude/skills/babysit-pr in owid/etl) into .claude/skills/babysit-pr in your project. Claude Code loads it when a task matches its description.

How do I install Babysit PR in Codex?

Run `npx skills add owid/etl --skill babysit-pr -a codex`. Or copy the skill folder (.claude/skills/babysit-pr in owid/etl) into .agents/skills/babysit-pr in your project. Codex loads it when a task matches its description.

Can I use Babysit PR in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add owid/etl --skill babysit-pr -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/babysit-pr, .gemini/skills/babysit-pr, .github/skills/babysit-pr and .opencode/skills/babysit-pr in your project.

What does Babysit PR need to run?

Going by SKILL.md and its folder, Babysit PR needs the command-line tools its instructions call (gh, git, jq, cursor and make).

Does Babysit PR access the network?

SKILL.md contains no URLs. Its commands use gh and git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Babysit PR safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Babysit PR use?

Babysit PR is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Babysit PR use?

About 8.9k tokens (SKILL.md is roughly 36k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Babysit PR?

Skills that share tags, products or a category with Babysit PR: Exploratory Data Analysis (spacering-net/codeg, 3.8k stars), Matplotlib (zLanqing/codex-claude-academic-skills, 4.6k stars), Scikit Learn (zLanqing/codex-claude-academic-skills, 4.6k stars) and Chart Visualization (bytedance/deer-flow, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Babysit PR?

owid (a GitHub organization) maintains it in owid/etl, which has 158 GitHub stars. The repository holds 35 skills in this directory. The repository was last updated on October 8, 2026.

Source: owid/etl on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.