| name | fix-pr |
| description | Fix GitHub PR issues — address review comments and resolve CI failures in a loop until the PR is fully clean. Fetches CI errors online and triages review feedback. Use when fixing PR problems, addressing review comments, or resolving CI failures. |
Fix PR Workflow
Fix PR issues (review comments, CI failures) in a loop until the PR is fully clean.
Task Tracking
Create tasks to track progress through this workflow:
- Match input to PR
- Detect & classify issues
- Get user confirmation
- Fix issues & push
- Resolve comment threads
- Re-check (loop until clean)
Input
Accept PR number (123, #123), branch name, or no argument (uses current branch).
Setup
-
Setup — authenticate and detect context (role, remotes, state)
-
Auto-detect cross-fork PR context: If no PR number provided, check upstream tracking to detect cross-fork push target:
UPSTREAM=$(git rev-parse --abbrev-ref "@{upstream}" 2>/dev/null || echo "")
if [ -n "$UPSTREAM" ]; then
UPSTREAM_REMOTE=$(echo "$UPSTREAM" | cut -d'/' -f1)
if [ "$UPSTREAM_REMOTE" != "origin" ] && [ "$UPSTREAM_REMOTE" != "upstream" ]; then
PUSH_REMOTE="$UPSTREAM_REMOTE"
HEAD_BRANCH=$(echo "$UPSTREAM" | cut -d'/' -f2-)
fi
fi
Loop: Steps 1→8, repeat until clean or max 5 iterations
Step 1: Match Input to PR
Use lookup-pr to find the PR.
- If PR number or branch name provided: use "By PR number" or "By branch name" lookup
- If no input and cross-fork detected (from Setup): search with
--head "$UPSTREAM_REMOTE:$HEAD_BRANCH"
- If no input and no cross-fork: auto-detect from current branch, or list open PRs for user selection
Validate PR state: OPEN (continue), CLOSED (warn), MERGED (exit).
Step 2: Detect Issues (run in parallel)
A) Review comments:
Run fetch-comments.
OWNER=$(gh repo view --json owner -q '.owner.login')
NAME=$(gh repo view --json name -q '.name')
gh api graphql \
-F owner="$OWNER" -F name="$NAME" -F number=<NUMBER> \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id isResolved
comments(last: 1) {
nodes { id databaseId body author { login } path line }
}
}
pageInfo { hasNextPage endCursor }
}
}
}
}' > /tmp/threads.json
grep -Ec '"isResolved":[[:space:]]*false' /tmp/threads.json
B) CI status:
gh pr checks <NUMBER>
Shell pitfalls to avoid:
- Do NOT pipe
gh api graphql to python3 -c with json.load(sys.stdin) — gh may emit extra metadata that breaks JSON parsing with JSONDecodeError: Extra data
- Do NOT use
gh api graphql --jq with $ in filter expressions — gh's jq processor interprets $ as a jq variable sign, causing Expected VAR_SIGN errors even when shell quoting is correct
- Also see common-issues for
gh api quoting pitfalls (use single quotes for --jq and -f body=)
- Use
grep -c for simple counts; save to a temp file first if complex parsing is needed
Present: "Iteration N — Found X unresolved comments and Y failed/pending checks."
Exit condition: All checks green AND no unresolved comments → done. Pending checks do NOT count as clean.
Step 3: Detect Permission
Run detect-permission to determine push access.
Step 4: Classify Issues
Review comments — filter isResolved: false, classify:
| Category | Description | Examples |
|---|
| A: Actionable | Code changes required | Bugs, missing validation, race conditions, incorrect logic |
| B: Discussable | May skip if follows .claude/rules/ | Style preferences, premature optimizations |
| C: Informational | Resolve without changes | Acknowledgments, "optional" suggestions |
Treat bot reviewers (CodeRabbit, Copilot, Gemini) same as human — classify by content.
For Category B, explain why code may already comply with .claude/rules/.
CI failures:
gh pr checks <NUMBER> --json name,state,link
RUN_ID=$(echo "$LINK" | sed -En 's|.*/runs/([0-9]+)/.*|\1|p')
JOB_ID=$(echo "$LINK" | sed -En 's|.*/job/([0-9]+).*|\1|p')
gh run view "$RUN_ID" --log-failed
gh run view --job "$JOB_ID" --log-failed
gh run view <RUN_ID> --log-failed requires the entire run to be complete (all jobs, not just the failed one). If any job is still pending, gh returns "run is still in progress". Check first: gh run view <RUN_ID> --json status --jq '.status' — must return "completed".
When the run is still partially running but some jobs have already failed, prefer gh run view --job <JOB_ID> --log-failed — it pulls the single job's log without waiting for siblings. List job IDs with gh run view <RUN_ID> --json jobs --jq '.jobs[] | {name, status, conclusion, databaseId}'.
For large logs: gh run view --job <JOB_ID> --log-failed 2>&1 | grep -E "error:|FAILED|fatal" | head -20
External checks (non-GitHub Actions): no run ID exists — open the link URL directly to view logs from the external provider.
Step 5: Get User Confirmation
Present ALL issues in a numbered list:
Review Comments:
1. [A] src/foo.cpp:42 — Missing null check (reviewer: alice)
2. [B] src/bar.py:15 — Style suggestion (reviewer: coderabbitai)
CI Failures:
3. [CI] build — error: 'Foo' is not a member of 'pto2'
Ask which to address/skip:
- Recommend addressing Category A + CI items
- Mark Category B with rationale for skipping or addressing
- Mark Category C as skippable by default
User choices per comment: Address (make changes) / Skip (resolve as-is) / Discuss (need clarification)
Only proceed with the comments the user explicitly selects. Do NOT auto-resolve any comment without user consent.
On subsequent iterations, reuse prior "address all" policy for same categories. When unsure about a comment's category, default to B.
Step 6: Work Location Setup & Fix Issues
Work directly on the PR branch. Setup depends on permission level:
For owner/write permission:
git checkout $HEAD_BRANCH
git pull "$PUSH_REMOTE" "$HEAD_BRANCH"
For maintainer permission (cross-fork PR):
Run checkout-fork-branch to create/switch to the local working branch and set the push refspec.
Fix:
- Read affected files, make changes with Edit tool
- For CI: analyze logs online first, reproduce locally only as last resort
- Commit using
/git-commit skill (skip testing/review for minor fixes)
Then run commit-and-push:
- Rebase onto
$BASE_REF
- Ensure single valid commit (squash with original PR commit)
- Push (update push with
--force-with-lease to $PUSH_REMOTE)
Commit message: fix(pr): resolve issues for #<number> with bullet list of fixes.
Step 7: Reply and Resolve Comment Threads
For each comment, both steps are mandatory (see reply-and-resolve):
-
Reply using the comment's databaseId:
gh api "repos/${PR_REPO_OWNER}/${PR_REPO_NAME}/pulls/${PR_NUMBER}/comments/${COMMENT_DATABASE_ID}/replies" \
-f body='Fixed — description of change'
-
Resolve the thread using the thread's GraphQL node id (from fetch-comments, NOT the databaseId):
gh api graphql -f query='
mutation { resolveReviewThread(input: {threadId: "THREAD_NODE_ID"}) {
thread { isResolved }
}}'
Important: The thread id comes from reviewThreads.nodes[].id in the fetch-comments GraphQL response. Each thread contains comments — use the thread's id to resolve, and the comment's databaseId to reply.
Reply templates:
- Fixed → "Fixed in
<commit> — description of change"
- Skip → "Follows
.claude/rules/<file> — explanation"
- Ack → "Acknowledged!"
Step 8: Wait and Re-check
gh run view <RUN_ID> --json status --jq '.status'
Poll with gh pr checks <NUMBER> — proceed early if all checks finish. For whole-run logs, wait until status is "completed". If a job has already failed and you only need that job's output, fetch it now via gh run view --job <JOB_ID> --log-failed (see Step 4) rather than blocking on the rest of the run.
Then loop back to Step 2.
Loop safeguards: Max 5 iterations. Flag stuck issues (same failure reappears) to user instead of retrying.
Reference Tables
| Area | Guidelines |
|---|
| CI errors | Fetch logs online first; reproduce locally as last resort |
| Bot reviews | Classify by content, not author |
| Changes | Read full context; minimal edits; follow project conventions |
| Error | Action |
|---|
| PR not found | gh pr list; ask user |
| CI logs unavailable / run in progress | Wait for run completion; if still unavailable, fall back to local reproduction |
| CI logs too large | grep -E "error:|FAILED|fatal" |
| Max iterations reached | Stop, report remaining issues |
| Same failure persists | Flag to user, do not retry |
Checklist
Remember
Not all comments require code changes. Evaluate against .claude/rules/ first. When in doubt, consult user.