| name | automate-pr |
| description | Drive an already-open PR to merge-ready: fix CI failures, resolve GitHub Copilot review comments, then loop @sdk-review (mothership) — fixing every finding it reports, including nits — until it comes back clean. |
/automate-pr
Prerequisite: a PR is already open for the branch you're working on. This
skill does not create PRs — it drives an existing one to a mergeable state.
Usage
/automate-pr # operates on the current branch's open PR
/automate-pr 1234 # operate on PR #1234
/automate-pr <url> # operate on PR by URL
Overview
Four phases, run in order:
- CI green — fix any failing checks.
- Copilot clean — resolve every unresolved GitHub Copilot review comment.
- Mothership clean — post
@sdk-review, wait for the real bot reply, fix
every finding it lists (including nits), push, and repeat until it reports
zero findings.
- Report — summarize what changed.
This is a real GitHub-in-the-loop workflow. Never fabricate a CI result or a
mothership comment — always read the actual state from gh/the GitHub API
before deciding the next action.
Phase 0: Identify the PR
gh pr view $ARG --json number,headRefName,baseRefName,url,headRefOid,isDraft
gh repo view --json nameWithOwner --jq .nameWithOwner
Store PR_NUMBER, HEAD_BRANCH, BASE_BRANCH, REPO (owner/name). Confirm
gh auth status is logged in before doing anything else.
If the PR is a draft, note it but proceed — the point of this skill is to get
it into mergeable shape.
Phase 1: CI green
gh pr checks "$PR_NUMBER" --repo "$REPO" --watch
For each failing required check:
- Find the run:
gh pr checks "$PR_NUMBER" --repo "$REPO" --json name,state,link,workflow.
Each failing check's link is a URL like .../actions/runs/<RUN_ID>/job/<JOB_ID>
— extract RUN_ID with grep -oE 'runs/[0-9]+' | cut -d/ -f2.
- Read the failure:
gh run view <RUN_ID> --repo "$REPO" --log-failed
- Decide:
- In PR code, obvious fix (pre-commit/lint, type error, missing import,
a unit test broken by this PR's change) → fix it.
- Pre-existing / infra failure (flaky test unrelated to the diff, OOM,
timeout, an already-red check on
main) → do not touch it. Note it and
move on — do not let it block the loop, but call it out in the final
report.
- If you changed files: run
uv run pre-commit run --files <changed files>,
then relevant tests (uv run pytest ...), then commit and push (see
Commit conventions below).
- Re-run
gh pr checks --watch and repeat.
Cap this at 5 fix attempts. If CI is still red on required checks after 5
attempts and the failures aren't clearly pre-existing, stop and ask the user
how to proceed rather than guessing further.
Do not proceed to Phase 2 while a required check is failing for a reason
inside this PR's diff.
Phase 2: Resolve GitHub Copilot review comments
Copilot's automated PR review posts as a bot account whose login contains
copilot (e.g. copilot-pull-request-reviewer[bot]). Fetch unresolved
threads from that author via GraphQL:
gh api graphql -f query='
query($owner:String!, $repo:String!, $pr:Int!) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$pr) {
reviewThreads(first:100) {
nodes {
id
isResolved
isOutdated
comments(first:10) {
nodes {
id
databaseId
author { login }
body
path
line
originalLine
diffHunk
url
}
}
}
}
}
}
}' -f owner="${REPO%/*}" -f repo="${REPO#*/}" -F pr="$PR_NUMBER"
- Keep threads where
isResolved == false && isOutdated == false and the
first comment's author.login matches copilot (case-insensitive).
- If none remain, skip to Phase 3.
For each unresolved Copilot thread (COMMENT_DATABASE_ID = the comment's
databaseId, THREAD_ID = the thread's id, both from the GraphQL query
above):
- Read the comment body, the
diffHunk, and the actual file at path
around line/originalLine. Pull in more surrounding context/callers if
needed to judge it properly.
- Decide fix vs. false positive:
- Fix: the concern is valid — edit the code. Minimal, targeted change;
don't refactor around it.
- False positive: not applicable, already handled, or intentional —
no code change.
- Always reply on the thread (this is required either way):
gh api repos/$REPO/pulls/$PR_NUMBER/comments/$COMMENT_DATABASE_ID/replies \
-f body="<reply>"
- If fixed: a short factual note, e.g. "Fixed — now checks for
None
before dereferencing." (no need to over-explain).
- If dismissed: a concise rationale, e.g. "False positive:
x is
validated non-null by the caller in foo.py:42."
- Resolve the thread. Replying does not mark it resolved, so it will
keep reappearing on the next fetch. Use the thread's
id (from the
GraphQL query, not databaseId) to resolve it explicitly:
gh api graphql -f query='
mutation($id: ID!) {
resolveReviewThread(input: {threadId: $id}) { thread { isResolved } }
}' -f id="$THREAD_ID"
After working through all threads, if any code changed: uv run pre-commit run --files <changed>, commit, push (see Commit conventions).
Copilot may post a fresh review after your push. Re-run the fetch once more
after pushing; if new unresolved Copilot threads appear, repeat. Cap at 3
rounds — if still unresolved after that, note it in the final report and
move on to Phase 3 rather than looping forever.
Phase 3: Mothership (@sdk-review) loop
This is the core loop. @sdk-review dispatches a real bot (mothership-ai)
that reviews the PR out-of-band and posts a comment back — this can take
several minutes. Wait for the actual comment; never simulate or guess its
contents.
3a. Trigger
TRIGGER_URL=$(gh pr comment "$PR_NUMBER" --repo "$REPO" --body "@sdk-review")
TRIGGER_ID="${TRIGGER_URL##*issuecomment-}"
TRIGGER_TIME=$(gh api "repos/$REPO/issues/comments/$TRIGGER_ID" --jq '.created_at')
3b. Wait for the reply
Poll for a comment created after TRIGGER_TIME, authored by a login
containing mothership, whose body contains the <!-- SDK_REVIEW -->
marker. --jq runs per-page under --paginate, so filtering and taking
last in the same expression only gives you the last match of each page,
not overall — filter per-page instead, then slurp all pages and take the
last match across the whole result:
gh api "repos/$REPO/issues/$PR_NUMBER/comments" --paginate \
--jq --arg after "$TRIGGER_TIME" \
'.[] | select(.created_at > $after)
| select(.user.login | test("mothership"; "i"))
| select(.body | contains("<!-- SDK_REVIEW -->"))' \
| jq -s 'last' > /tmp/mothership_reply.json
Poll every ~30s. A review can take up to ~35 minutes on a large PR, so budget
accordingly: run this check in a loop inside one Bash call for a few minutes,
and if nothing shows up, issue the same check again — repeat up to a total
wait of ~40 minutes. If nothing arrives after that, stop and tell the user
the mothership workflow may not have fired (check the repo's Actions tab)
rather than waiting indefinitely.
Never treat CI comments, human comments, or old sdk-review comments as
the reply. It must be newly created after TRIGGER_TIME.
3c. Read the verdict
Read the full comment body from the object saved in 3b (jq -r '.body' /tmp/mothership_reply.json) — don't truncate with naive head -1/single-line
jq selectors, the body is multiline. Extract:
- The
<!-- VERDICT: X --> marker.
- The
### Findings section — every bullet under it, grouped by file, at
every severity (Critical, Important, Nit).
Stopping condition: if ### Findings is absent, says there are no
findings, or has zero bullets, the PR is clean — stop the loop and go to
Phase 4. This bar is stricter than the bot's own VERDICT (which tolerates
nits) — per this skill's requirement, nits count too.
3d. Fix every finding
For each finding bullet (yes, including every Nit):
- Locate the file and line it points to.
- Read enough surrounding context to understand it correctly.
- Apply the fix the bullet's
*Path: ...* clause describes. Make the
minimal change that addresses it — don't use it as an excuse to refactor
nearby code.
- If a finding is genuinely ambiguous — e.g. it explicitly presents two
valid design options with no clear winner — use
AskUserQuestion once to
get a decision rather than guessing. Don't stall the whole loop on this;
ask, get the answer, keep going.
- If a finding turns out to already be resolved (code no longer matches
what's described, e.g. from an earlier Copilot fix) or is a duplicate,
skip it and note why in the round summary — don't silently drop findings
without a reason.
3e. Commit, push, re-verify CI
uv run pre-commit run --files <changed files>
uv run pytest <relevant test paths> -x
git add <specific changed files>
git commit -m "fix(review): address sdk-review findings (round N)"
git push
Then re-run Phase 1's CI-green loop on the new push before re-triggering
— don't burn a mothership review cycle on a CI failure you already know
about.
3f. Repeat
Go back to 3a (post @sdk-review again) and continue until 3c's stopping
condition is met.
Cap this at 8 rounds total. If findings still remain after 8 rounds, stop
and report exactly what's left, and ask the user how to proceed — don't loop
forever on something that isn't converging.
Commit conventions
- Conventional Commits format (
fix:, chore:, fix(review):, etc.) per
docs/standards/commits.md.
- Never add a
Co-Authored-By line — this repo's CLAUDE.md explicitly
overrides the default.
- Never edit
CHANGELOG.md — it's CI-generated.
- Stage specific files (
git add <paths>), never git add -A/..
- Never force-push.
- Always run
uv run pre-commit run --files <changed> before committing —
CI will fail if pre-commit fails.
Phase 4: Report
Summarize for the user:
- CI: what failed and what was fixed (or noted as pre-existing).
- Copilot: how many comments fixed vs. dismissed, and how many rounds it took.
- Mothership: how many
@sdk-review rounds it took, how many findings were
fixed per round, final verdict.
- Any open questions raised via
AskUserQuestion and how they were resolved.
- The PR URL.
Constraints
- Only touch code needed to address a CI failure, a Copilot comment, or a
mothership finding — no drive-by cleanup.
- Never fabricate tool output. If
gh returns nothing, that means nothing
happened yet — keep waiting or report the stall, don't invent a result.
- Never skip pre-commit or test hooks (
--no-verify) unless the user
explicitly asks.
- Confidentiality: don't put customer/tenant/run-ID identifiers in commit
messages or replies — reference issues generically.