Monitor a PR and fix CI or review feedback. Use standalone, or from /ship to continue through its authorized guarded merge instead of stopping at green.
57
72%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/babysit-pr/SKILL.mdMonitor PR #$ARGUMENTS in the current repo and fix CI failures and human or bot
review feedback. A standalone /babysit-pr may stop after 30 minutes of green
CI and no new feedback. When invoked by /ship, honor its inherited
ship_mode in this foreground task. /ship and standalone /babysit-pr stay
foreground-only; do not create or resume a durable watcher or use PR leases.
A request to monitor or fix a PR does not authorize pushing to a PR authored by someone else. Push to that PR only when the user explicitly authorizes a push to that exact PR in the current request. A linked PR or inherited general ship authorization is insufficient. That permits only a push; merging that PR needs separate authorization in the current request. Before an authorized push, verify the live PR author, head repository, branch, head OID, and base; recheck the head before every normal fast-forward push.
Before each push, resolve the active GitHub login with gh api user --jq .login,
include author in the live PR query, and compare author.login with that
login. If they differ, require the current-request authorization for that exact
PR.
A worktree is a valid PR checkout. When monitoring from one, keep Git and GitHub commands in that worktree's cwd and current branch; do not copy changes to the shared checkout or require that an agent publish from the root checkout.
In a shared checkout, keep its branch unchanged. When the user explicitly asks
to update this PR and the checkout cannot safely serve as its source, follow
new-branch to isolate the PR work in a managed task-owned worktree without
asking. Carry only changes belonging to this PR. If they cannot be isolated
safely, preserve state and report the exact paths or commits without asking
for branch or worktree permission.
For an existing PR update, record headRepository.nameWithOwner, headRefName,
and headRefOid, then base the isolated task branch on that exact head. Resolve
a remote for that exact head repository; origin is correct only when its URL
matches. If needed, add a uniquely named remote for the verified head
repository. Never infer the push target from the base repository or a matching
branch name. If no writable head repository is available, preserve state and
report the blocker.
ship:push always commits publishable paths and pushes origin/<local branch>.
Use it for an existing PR only when origin matches the recorded head
repository and the local branch name equals headRefName. Otherwise, stage
only this task's paths, commit with a specific subject, and push
HEAD:refs/heads/$headRefName to head_remote after rechecking the live OID.
Before each push, inspect any worktree using that head branch for unpublished
PR commits. If the head moved, follow Setup's non-fast-forward recovery: fetch
the refreshed head and merge it into the clean task branch, resolve and test,
then recheck before pushing. Preserve state and report only when peer or
unpublished work, or conflicts that cannot be safely resolved, block recovery.
Never replace an existing PR's history, rebase, or force-push.
During /babysit-pr, the PR remains the unit of review and the shared checkout
is the branch snapshot. At the first tick, record dirty paths and unpushed
commits; publish the requested initial work only after verifying that every
candidate belongs to this PR's requested fix. If unrelated or incomplete
concurrent work is present in a shared checkout, continue in the managed
worktree using the existing PR's live head and carry only this task's fixes.
Preserve the shared checkout. If safe isolation is impossible, report exact
paths or commits without asking. On later ticks, inspect the tree before every
push. Publish only a complete, coherent set of currently known fixes for
failing CI, PR feedback, a real merge conflict, or an explicit user request.
Batch multiple feedback items and delegate changes into one update; do not
create a commit for each finding, checkpoint, or timer tick. Every new head
reruns affected checks and resets the soak. Publish to the verified PR target
as described above; ship:push rejects an omitted or generic subject. A clean
tree, origin/main drift, queued checks, or a timer tick is not a reason to
commit or push. Never publish unrelated concurrent work, and never revert,
stash, or overwrite it.
When an actionable fix is changing, collect all known related CI and review
findings, validate the combined fix, then publish one coherent snapshot to the
existing PR. Do not push incremental snapshots just to start CI early; the
latest head needs a stable run before merge. The final clean-tree and
merge-soak gates still apply before merging, except when the user explicitly
invokes /ship-now.
If no PR number is given, auto-detect it: get the current branch (git branch --show-current), find the open PR for it (gh pr list --head <branch> --state open --json number --limit 1). If no open PR exists, check recent merged/closed PRs. Only ask the user if no PR can be found.
At the start and on every resumed tick, resolve the active GitHub login and query the PR, including its author:
gh api user --jq .login
gh pr view <number> --json state,mergedAt,closedAt,author,headRepository,headRepositoryOwner,headRefName,headRefOid,baseRefName,mergeCommitIf the query fails or is ambiguous, stay foreground-only until its state is
known. A closed, unmerged PR ends babysitting and is reported as unsuccessful.
If a PR is already merged under inherited ship_mode=merge-authorized,
continue the post-merge path here. Standalone and ready-only invocations report
an unexpected merge without rotating.
headRepository.nameWithOwner, headRefName, and
headRefOid. Resolve the writable push remote by matching its URL to that
exact repository; origin is valid only when it matches. If none exists,
add a uniquely named remote for the exact head repository. Fetch the PR
head from that remote at setup and on each tick. On a non-fast-forward
rejection or changed headRefOid, verify the fetched head against live PR
metadata. If it already contains the local commits, do not push them again.
Otherwise confirm the tree is clean and those commits belong to this PR,
merge the refreshed <head_remote>/<headRefName> into the current branch,
resolve and test, then recheck the live head before pushing normally to the
same remote and ref. Never retry the same stale push, rebase, or force-push.
If peer or unpublished work or unresolvable conflicts block recovery,
preserve state and report exact blockers. Never
update from origin/main unless GitHub reports a confirmed CONFLICTING
PR; then use a normal merge, never a rebase. A behind count or pending
checks are not conflicts. Guard PR merges with
--match-head-commit <live_head_oid>; a stale-head rejection is a retry
signal, not a reason to stop the requested work./babysit-pr, stop after 30 minutes with green GitHub Actions
CI and no new actionable item. Under /ship with ship_mode=merge-authorized,
keep working through the 10-minute merge gate and guarded merge. With
ship_mode=ready-only, fix CI and review feedback until the ready-PR gate
holds, then leave the PR open.After an actionable fix or push, reset the applicable clock: standalone's
30-minute quiet-green timer or /ship's 10-minute merge soak. The /ship soak
starts only once every merge condition below holds; it never waits for 30
minutes of quiet.
/ship because CI, review, or
a background command is pending. Use short interruptible waits and check
again in this task.Step 0 — always do this first, before anything else:
if ! git fetch origin --quiet; then
echo "Cannot refresh origin refs; stop before checking unpublished commits." >&2
exit 1
fiImmediately resolve the active GitHub login with gh api user --jq .login,
then query the live PR state with
gh pr view $ARGUMENTS --json state,mergedAt,closedAt,author,headRepository,headRepositoryOwner,headRefName,headRefOid,baseRefName,mergeCommit.
If the query fails, do not run branch, review, or CI checks; retry on the next
foreground tick. A closed but unmerged PR ends babysitting and is reported as
unsuccessful. A merged PR is a
terminal state for standalone /babysit-pr and inherited ship_mode=ready-only
(report the unexpected merge and do not rotate). Under inherited
ship_mode=merge-authorized, continue the /ship post-merge path below before
cleanup. Never treat PR merge alone as completion of the parent ship goal.
For an open PR, resolve head_remote by matching a configured remote URL to
headRepository.nameWithOwner; origin is valid only when it matches. If no
remote exists, add a uniquely named one for the exact head repository. Fetch
the live head with an explicit refspec into
refs/remotes/$head_remote/$headRefName, then verify that ref's OID matches
live PR metadata before using it as the task branch base, comparison ref, or
push target. If no writable head repository is available, preserve state and
report that blocker.
For an open PR, inspect the branch snapshot:
git fetch "$head_remote" "refs/heads/$headRefName:refs/remotes/$head_remote/$headRefName"
git status --short
git diff --name-only
if git show-ref --verify --quiet "refs/remotes/$head_remote/$headRefName"; then
git log --oneline --decorate "refs/remotes/$head_remote/$headRefName"..HEAD -- . ':(exclude)learnings.md' ':(exclude)bridge/**' ':(exclude)data/**'
else
git log --oneline --decorate HEAD --not --remotes="$head_remote" -- . ':(exclude)learnings.md' ':(exclude)bridge/**' ':(exclude)data/**'
fiAfter the status check, publish only an intentional fix for a concrete CI failure, PR feedback, merge conflict, or explicit user request, using the verified target path above. If the tree is clean and already pushed, do nothing. If it is clean with unpushed commits, push them directly only when those commits are already an intentional actionable fix; never create a maintenance commit merely to make the branch look current.
Every tick starts here, no exceptions: on an active shared branch local files can change within minutes, so re-check before every actionable push.
Never git stash concurrent changes. Stashes get orphaned, and a stash named babysit-tickN-concurrent-work-* left on the source branch while babysit-pr's PR ships without it is exactly how real work gets lost. If you see local changes you don't recognize, preserve them for their owner; do not hide them in a stash or commit them here.
Step 1 — check for merge conflicts:
Run gh pr view $ARGUMENTS --json mergeable --jq '.mergeable'.
If CONFLICTING: bring main in and resolve. First inspect the worktree
and unpushed commits; do not run the merge until the publishable-path check
git status --short -- . ':(exclude)learnings.md' ':(exclude)bridge/**' ':(exclude)data/**' and the branch-specific unpublished-commit check in
Step 0 are empty. The unpublished-commit check is also scoped to
publishable paths, so excluded-only commits do not block recovery. Preserve
and report excluded paths; they do not block
recovery unless the merge itself touches them.
Before merging, compare the local checkout with the live PR head:
pr_head=$(gh pr view $ARGUMENTS --json headRefOid --jq '.headRefOid')
if [ "$(git rev-parse HEAD)" != "$pr_head" ]; then
echo "Local HEAD is not the live PR head; stop and let the branch owner reconcile it." >&2
exit 1
fiIf either publishable-path check is non-empty, do not attempt an in-place
isolation. Preserve the exact dirty paths and unpublished commits, leave the
checkout untouched, and wait for the owning session to publish or move its
work. A separate clean PR worktree may perform this recovery when one is
already available. Never use git stash, reset, restore, or a temporary
branch as a substitute for retaining concurrent work.
Do not merge an obsolete local head.
Publish any intentional actionable fix first (Step 0), after verifying
every dirty path and unpushed commit belongs to that fix; then prefer a
merge over a rebase —
git fetch origin main && git merge --no-edit origin/main — because this branch is shared with concurrent agents and a
rebase would rewrite history and require a force-push that can clobber their
unpushed commits. Resolve the conflicts (for pnpm-lock.yaml, take one side
with git checkout --theirs -- pnpm-lock.yaml then regenerate with pnpm install --lockfile-only against the merged package.json), complete the
merge commit, and push (a normal push, never --force). This resets the soak
timer. If unrelated or incomplete concurrent work keeps the worktree dirty,
preserve it and wait for its owner instead of stashing, restoring, or
forcing the merge. Do not merge origin/main again while the PR is
MERGEABLE or UNKNOWN, or while checks are merely pending; a conflict-free
PR does not need another main sync. Merge from main only to resolve a
confirmed conflict. Because this branch is shared, use a normal merge;
never rebase.
If MERGEABLE or UNKNOWN: proceed. (mergeStateStatus: BLOCKED with mergeable: MERGEABLE just means required checks are still pending/red — that is not a conflict; keep going.)
If the PR body or branch cites /review-latest-feedback, treat its start
cursor, grouped reports, evidence links, and disposition table as part of the
PR's review state. At the first tick, record that handoff. On every later tick
before the merge gate, re-read the handoff and check for new Slack replies,
GitHub feedback, and Sentry findings after its cursor using the configured
connectors. Re-query first-party Agent-Native Analytics error issues with
list-error-issues and its available filters; it has no time cursor and caps
results at 100, so record bounded coverage and do not claim exhaustive newness.
A new actionable report resets the soak timer and must reach either
a verified Fixed result with a concise reply and ✅, a verified
Shipped result with a concise reply, a verified Live verified result
(reply only when informative), or a non-fixed terminal ledger disposition
with its existing 👀 before merge (✅ only for Fixed). An
active/evidence-limited disposition or a reply without one of those outcomes
blocks merge; the eye remains on every claimed item. Reactions are append-only;
newer thread evidence controls the current disposition.
Evidence-limited or active dispositions retain the workflow's eye; it remains
after resolution. Keep active and terminal state in thread text or linked
work; do not repeat a status already recorded. If a connector is unavailable,
record it as unavailable in the recap rather than treating it as no findings.
Then proceed with PR checks:
Check for review comments and review summaries from humans and bots — EVERY tick, with no exceptions.
⚠️ Review bots (Builder, Copilot, etc.) RE-REVIEW on every push and post a brand-new round of comments each time. A PR commonly accumulates several rounds. You MUST re-check on every single tick — including "quiet" ticks where you're only waiting on CI — and you must keep checking right up until the moment you merge.
Never filter comments by a "since " window. A forward-looking timestamp silently skips rounds that were posted before your last reply (e.g. a round that landed between the first review and when you replied), and "0 new since X" reads as "all addressed" when it is not. This exact mistake left two whole review rounds unanswered on PR #1097 (2026-06-08).
Instead, determine coverage by reply state: list every top-level review comment that does not yet have a reply, across all pages and all rounds. Stream every comment with --jq '.[]' (concatenates cleanly across pages), then slurp:
gh api --paginate repos/{owner}/{repo}/pulls/$ARGUMENTS/comments --jq '.[]' \
| jq -s '
([ .[] | .in_reply_to_id // empty ]) as $replied
| .[]
| select((.in_reply_to_id // null) == null) # top-level comments only
| select(.id as $id | ($replied | index($id)) | not) # …with no reply yet
| {id, user: .user.login, path, line: (.line // .original_line), snippet: (.body[0:200])}'(Bind the id with .id as $id first — index(.id) would evaluate .id against the $replied array, not the comment, and error out.) If that command prints anything, there is unaddressed feedback — fix or reply to each (see "Responding to feedback") before you consider the PR clean. Also re-read the latest review summary bodies each tick (bots restate their findings here):
gh api repos/{owner}/{repo}/pulls/$ARGUMENTS/reviews --jq '.[] | select(.body != null and .body != "") | {user: .user.login, state, submitted_at, body: .body[0:1000]}'Treat the count of unaddressed comments (not a timestamp) as the source of truth for "is there feedback to handle".
Check CI status:
gh pr checks $ARGUMENTSIf new human or bot feedback includes real bugs or requested changes:
pnpm run prep to verify locallyIf GitHub Actions CI is failing (lint, test, typecheck, build):
pnpm run prep locallySpecial case: missing changeset. If the Lint & format job fails at its Require changeset for publishable package changes step (in .github/workflows/ci.yml), do NOT treat it as a code bug. The step log includes a structured line MISSING_CHANGESET_PACKAGES: pkg1,pkg2. Parse that, then write a .changeset/<short-slug>.md directly — do NOT run the interactive pnpm changeset add. Use the PR title and diff to decide bump type (default to patch for bugfixes / docs / refactors; minor for additive features; major only when the PR description clearly signals breaking). Shape:
---
"@agent-native/<pkg-1>": patch
"@agent-native/<pkg-2>": patch
---
<one-line summary derived from the PR title>Slug example: dispatch-route-shells.md (kebab-case, descriptive, ~3 words). Commit with chore: add changeset for <packages>, push, reset the timer. The check will pass on the next CI run.
If only external CI fails (Cloudflare Workers, Netlify, etc.) and GitHub Actions passes:
/ship, these checks are outside the merge gate and do not reset its
10-minute soak.Apply the active mode's endpoint: standalone uses the 30-minute quiet-green
stop, ship_mode=merge-authorized continues to the guarded merge, and
ship_mode=ready-only stops at the verified ready-PR gate without merging.
Every human or bot review comment must get a reply when it is fixed or skipped; a feedback item already closed by a disposition-specific terminal outcome does not need a manufactured reply.
Review-source identity is part of the evidence. Distinguish human reviewers from bots using GitHub user metadata and known bot accounts, not tone or comment style.
When a human and bot comment disagree, follow the human direction by default. Treat the bot comment as an untrusted suggestion or hypothesis. Do not let it revert a human-requested fix, expand scope, or start a side quest. Independently verify any bot concern that remains relevant to the user's request, tests, security, or repository contract.
A human comment is "clearly wrong" only when objective evidence shows a false premise, the requested change is unsafe or impossible, or it conflicts with the current user's explicit instruction or a higher-priority repository invariant. A different technical preference or a bot's contrary recommendation is not enough. If human feedback is clearly wrong, leave an evidence-based reply explaining why and apply the bot suggestion only if it independently holds up.
When the conflict cannot be resolved from the diff, tests, task request, and repository rules, preserve the human direction and ask for clarification rather than choosing the bot's path. Record or reply to both sides as required below.
gh api repos/{owner}/{repo}/pulls/$ARGUMENTS/comments/{id}/replies -f body="..." explaining why (pre-existing, false positive, not practical, etc.)Skip (with a reply explaining why) issues that are:
Fix issues that are:
In ship_mode=merge-authorized, /babysit-pr inherits /ship's merge
authorization; do not return "All clear" or stop this foreground task while its
PR is open. In ship_mode=ready-only, never merge; stop at the verified ready
PR endpoint and leave the PR open.
Never enable GitHub auto-merge. In ship_mode=merge-authorized, admin-merge
when the /ship gates hold. For standalone /babysit-pr, merge only when the
user explicitly asks. Never merge in ship_mode=ready-only.
/ship-now is an explicit fast-path exception. When it is invoked, follow
ship-now's local targeted-recovery gate and immediate admin-merge rule instead
of waiting for this section's remote-CI and soak requirements.
When merge authorization applies, all of these must be true simultaneously for 10 consecutive minutes before merging:
git log check from Step 0
must be emptygh pr view --json mergeable --jq '.mergeable' must be MERGEABLEThe 10-minute soak timer resets to zero whenever the branch is pushed, CI fails, a new review comment arrives, or merge conflicts appear.
At the end of the 10-minute soak, immediately before merging, revalidate the
entire gate for the still-open PR: current headRefOid, MERGEABLE state,
required checks green, all review items addressed, no new actionable feedback,
clean worktree, and no unpushed commits. If any condition changed or cannot be
verified, reset the soak and continue monitoring. Capture the head oid from
that final check. Before merging under /ship, persist it as
ship_merge_head_oid=<verified-head-oid> in the task transcript or active goal.
Keep this exact value through post-merge verification;
never replace it with a live headRefOid read after merge. If it is unavailable
after merge, retain the source branch rather than guessing.
Then run:
gh pr merge <number> --squash --admin --match-head-commit <verified-head-oid>If the head-match guard rejects the merge, restart the soak for the new head
and replace the saved head marker only after final revalidation. When the merge
succeeds under /ship, return the saved head marker and immutable
mergeCommit.oid to the parent. A resumed wake may recover the merge SHA from
the PR's immutable mergeCommit.oid; never recover the merged head from the
mutable live head ref.
/babysit-pr only: no new actionable feedback and GitHub
Actions green for 30 consecutive minutesship_mode=ready-only: the verified ready-PR endpoint aboveship_mode=merge-authorized, continue through the post-merge ship endpointIn ship_mode=merge-authorized, never stop at the 30-minute quiet-green
condition. Keep checking and fixing CI/review feedback in this task, merge as
soon as the 10-minute gate holds, then verify origin/main and finish branch
disposition. A closed but unmerged PR is a terminal state, not a successful
shipment. In ship_mode=ready-only, stop at the verified ready-PR endpoint,
leave the PR open, and do not merge or rotate.
/ship continuationWhen the foreground task finds the PR merged under inherited
ship_mode=merge-authorized, use the immutable ship_merge_head_oid saved
before merge and mergeCommit.oid from GitHub or the merge result. Never use
the current live headRefOid as the merged head. If the saved head marker is
missing, preserve the source branch. Continue through:
mergeCommit.oid is an ancestor of origin/main.
If it has not arrived yet, retry in the foreground at an interruptible
cadence of at most 60 seconds.ship_mode=merge-authorized, /ship authorizes the safe post-merge rotation
in this task-owned worktree; run /new-branch safety checks and rotate when
they pass. Retain the source branch if a check fails. In standalone
babysitting or ship_mode=ready-only, retain the source branch unless the
user requested that exact rotation in this task.The foreground task owns this continuation; no watcher or lease is required.
PR merge by itself is not parent handoff or goal completion. If the exact head OID is unavailable, preserve the source branch and report that safe disposition rather than guessing.
Before the guarded merge or ready-only endpoint, and before post-merge branch disposition, re-run the unaddressed inline-comments command and inspect every review body in this task:
gh api --paginate "repos/{owner}/{repo}/pulls/$ARGUMENTS/reviews" \
--jq '.[] | select(.body != "") | {id, user: .user.login, state, submitted_at, body}'Confirm every actionable item in the newest review summaries has a verified
fix and a reply, or a valid terminal disposition, including items without an
inline thread. New review feedback resets the merge soak. Do not stop in
ready-only mode or merge in merge-authorized mode until both the inline
thread audit and review-body audit are clear. "I replied earlier" is not
sufficient; bots may have posted new rounds since. If either final audit finds
new actionable feedback, fix it in the foreground and restart the soak while
the PR is still open. If it is already merged, record a
post-merge follow-up, retain the source branch, and do not restart the merged
PR's soak. Stop only after the endpoint is reached and both audits have a disposition.
A post-merge follow-up with the source branch retained is a valid final
disposition.
Verify the PR's final state.
a941a2e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.