pr-feedback
Address and resolve PR review feedback — find threads, classify findings, fix or reply, resolve threads via GitHub GraphQL, and know when NOT to re-request review.
SKILL.mdskills/pr-feedback/SKILL.md
GitHubPR Feedback — Address & Resolve
Playbook for the pr_review phase (and any post-PR-creation feedback loop). Covers the exact mechanics learned on real frndOS PRs: how to find every thread, classify each finding, apply fixes, reply, and formally resolve — plus the traps (no REST resolve API, reply-vs-overwrite, don't-trigger-re-review).
When to use
- Owner/bot posted review comments on an open PR (
pr_reviewphase: poll, classify, fix, resolve, loop). - User says "address PR feedback", "resolve all threads", or PR shows
CHANGES_REQUESTED.
Workflow
Step 1 — Inventory feedback
# PR-level comments (challenge/overview comments)
gh pr view <n> --repo <owner/repo> --json comments,reviews,reviewDecision \
-q '{decision: .reviewDecision, comments: [.comments[] | {author: .author.login, body: .body}]}'
# Inline review threads (the code-level findings)
gh api repos/<owner/repo>/pulls/<n>/comments \
--jq '.[] | {id: .id, path: .path, line: .line, body: .body[0:200]}'
# Formal thread state (resolved/outdated) — GraphQL ONLY, no REST equivalent
gh api graphql -f query='query { repository(owner:"O", name:"R") { pullRequest(number:N) {
reviewThreads(first:50) { nodes { id isResolved isOutdated comments(first:1) { nodes { body } } } } } } }'
Check every service PR in the feature — pr_urls in .workflow-state.json lists them (api, web, ai-service, data-service).
Step 2 — Classify each thread
| Class | Action |
|---|---|
| must-fix (security, data-loss, crash path, wrong behavior) | Fix code, push, reply, resolve |
| nit / style / low | Fix if cheap, else reply with rationale + resolve |
| question / design concern | Reply with decision + rationale; fix only if it changes the decision |
| already-correct claim (reviewer misread) | Reply with the fact (e.g. "social-listening IS in the 7 tabs"), resolve |
For each finding reply with the SPECIFIC fix, not "done". Template: Fixed: <what changed> — <why>. Verified: <how>.
Step 3 — Fix code
- Apply fixes on the feature branch.
- Verify locally: lint the changed files, re-run the affected command, confirm the guard/behavior both directions (e.g. prod-block exits non-zero AND local passes).
- Commit with
fix(<scope>): PR review — <summary>, push.
Gotchas that bit us before:
- Env guard fail-open:
env('APP_ENV', '')passes when APP_ENV is unset. Fail closed:getenv('APP_ENV') ?: config('app.env', 'production'), and treat empty string as production (?: 'production'). Rawenv('APP_ENV')is NULL when APP_ENV lives only in.env— config fallback keeps local runs working. - Idempotent seeding on refresh:
updateOrCreateoverwrites existing rows' real ids (e.g.fivetran_connection_id). UsefirstOrNew+ set demo ids only when!$row->exists. - Async CH mutations:
ALTER … DELETEis async; it can execute AFTER the follow-up INSERT and reap fresh rows. Wait for mutations (scoped to your tables viasystem.mutations WHERE database/table) before inserting. - Unique-constraint collisions: core+extended rows sharing
(brand, platform, connection_index)— extended must takemax+1and reuse existing rows by their real identity column.
Step 4 — Reply + resolve
GitHub has no REST endpoint to resolve threads — only the resolveReviewThread GraphQL mutation:
# reply on a thread (or PATCH body — see trap below)
gh api -X PATCH repos/<owner/repo>/pulls/comments/<comment-id> \
-f body="Fixed: <what> — <why>. Verified: <how>."
# resolve every unresolved thread
for tid in $(gh api graphql -f query='query{repository(owner:"O",name:"R"){pullRequest(number:N){reviewThreads(first:50){nodes{id isResolved}}}}}' \
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id'); do
gh api graphql -f query="mutation{resolveReviewThread(input:{threadId:\"$tid\"}){thread{isResolved}}}"
done
Reply to PR-level comments too: gh api repos/<owner/repo>/issues/<n>/comments -f body="..." (PR comments live under the issues endpoint).
Step 5 — Re-review policy
Do NOT re-request review unless the user explicitly says so. Default: fix, reply, resolve, then stop. The owner/bot re-reviews on their own schedule. (request-reviewers / re-request is only for when the user asks.)
Traps (all hit in real sessions)
PATCH /pulls/comments/{id}overwrites the comment body — the bot's original finding text is lost (author attribution stays the bot's). Prefer a reply comment (POST /pulls/{n}/commentswithin_reply_to) when you want history preserved; PATCH is fine when the final word per thread is enough.isResolvedonly exists in GraphQLreviewThreads; RESTgh pr viewcannot show it.- Shell quoting: GraphQL mutation strings with
$tidinside double quotes — use single-quoted heredoc-free one-liners or a script file. sedwith/in replacement text breaks — use|delimiters or avoid sed for such patches.- Threads on a push that changed the diff become
isOutdated: true— still resolve them explicitly; outdated ≠ resolved.
Verification checklist (before declaring done)
- Every thread: fixed or replied with rationale.
- Every thread:
isResolved == true(GraphQL check). - PR-level comments answered.
- Fixes verified locally (both guard directions, idempotent re-run).
- No re-review requested (unless user said so).
- Track file updated with the review round + fixes.
References
- Phase machine:
.agents/skills/workflow/SKILL.md—pr_reviewis run by the orchestrator, which loads this skill for the mechanics - Security audit before opening PRs: the external
security-reviewerskill (npx skills add https://github.com/jeffallan/claude-skills --skill security-reviewer), run duringpr_submission, not here - Commit conventions:
AGENTS.md→ "Commit Messages"