
Fusion Github Review Resolution
- 1.8k installs
- 1 repo stars
- Updated August 4, 2026
- equinor/fusion-skills
Automated resolution of unresolved GitHub PR review threads by evaluating comment validity, applying code fixes, posting replies with rationale, and resolving threads.
About
This skill automates the closure of unresolved GitHub PR review comments through a structured workflow: fetch threads, evaluate correctness of each comment against current code, apply focused fixes for valid feedback, reply with rationale or commit hashes, push changes once, and resolve threads. Developers use it when PRs have review feedback to address, needing an auditable, repeatable process that distinguishes between correct, outdated, and incorrect comments. Key workflows include parsing PR/review URLs to extract context, building a working checklist to track decisions, running targeted validation before committing, and using GitHub MCP or GraphQL tools to post replies and resolve threads atomically.
- Parses GitHub PR review URLs (#pullrequestreview-<id>) to auto-extract owner, repo, PR number, and review scope
- Evaluates each review comment against current code and requirements; does not apply changes for stale or incorrect feedb
- Creates one commit per validated comment and performs a single push after all fixes, reducing CI overhead
- Posts concrete replies naming changed files and commit hashes before resolving threads; never resolves without a reply
- Uses GitHub MCP tools or bundled GraphQL assets with dry-run guards and rate-limit awareness for thread mutations
Fusion Github Review Resolution by the numbers
- 1,842 all-time installs (skills.sh)
- +57 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #76 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
fusion-github-review-resolution capabilities & compatibility
- Capabilities
- parse pr and review urls · fetch unresolved review threads via github api · evaluate comment correctness against code logic · apply targeted code fixes · create and push commits · post replies to review threads with commit refer · resolve github review threads via graphql/mcp
- Works with
- github
- Use cases
- code review · debugging
- Platforms
- macOS · Linux · Windows
- Runs
- Runs locally
- Pricing
- Free
What fusion-github-review-resolution says it does
Follow this phase order unless the user explicitly asks for a different sequence: fetch → analyze → fix → validate → push → reply → resolve → verify.
npx skills add https://github.com/equinor/fusion-skills --skill fusion-github-review-resolutionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.8k |
|---|---|
| repo stars | ★ 1 |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | equinor/fusion-skills ↗ |
What it does
Resolve GitHub PR review threads end-to-end by evaluating comment validity, applying fixes, replying with rationale, and closing threads.
Who is it for?
PRs in 'changes requested' state; unresolved inline review threads; iterative feedback cycles where reviewers specify line-level fixes.
Skip if: Read-only branches; feedback summaries without code changes; PRs with no unresolved threads; new PR creation.
When should I use this skill?
User provides PR or review URL with unresolved threads, or explicitly requests review comment resolution or closure of open review conversations.
What you get
A PR with unresolved review comments becomes one with addressed feedback, documented replies, and closed review threads, ready for re-review or merge.
- Completed review-resolution-checklist.md with all comments evaluated and decisions logged
- Commits addressing valid review feedback
- GitHub thread replies (one per comment) posted and visible in PR
By the numbers
- Skill targets first 100 review threads and first 100 comments per thread when using bundled fetch script
- Each thread requires 1 reply mutation + 1 resolve mutation (2 GraphQL calls per thread at 5 points each)
Files
Resolve GitHub Review Comments
When to use
Use this skill when a pull request has unresolved inline review comments and you need a repeatable, auditable closure workflow.
Typical triggers (skill should activate on all of these):
URL patterns — activate immediately:
https://github.com/<owner>/<repo>/pull/<number>#pullrequestreview-<id>https://github.com/<owner>/<repo>/pull/<number>(when context implies review work)
Explicit user requests:
- "Fix the review comments"
- "Address the review feedback on this PR"
- "There are unresolved review comments — fix them"
- "Resolve the conversations on this PR"
- "Handle all unresolved comments on this PR"
- "For each review comment: fix, test, commit, reply, resolve"
- "Close the open review threads"
- "Check this review and resolve the conversation when fixed"
- "Resolve outstanding review feedback"
- "The PR has changes requested — fix it"
Implicit / agent-detected:
- A PR is in "changes requested" state and the agent is asked to work on it
- A PR has unresolved review threads and the user asks to improve or merge the PR
- An agent working on a PR detects open review conversations
When not to use
- No unresolved review comments exist
- Request is only to summarize or describe feedback without code changes
- All targeted threads are already resolved or outdated and acknowledged
- Branch/worktree is intentionally read-only
Required inputs
Collect before execution:
- repository owner/name
- pull request number or URL
- optional review id to scope comments (e.g.
pullrequestreview-<id>) - branch/worktree decision
- required validation commands for the repository
When a review URL is provided (github.com/<owner>/<repo>/pull/<number>#pullrequestreview-<id>),auto-extract owner, repo, PR number, and review id from it.
Only branch/worktree choice and validation commands still need confirming.
Optional:
- linked issue reference (e.g.
equinor/fusion-core-tasks#432) - commit granularity preference when comments overlap the same file
Instructions
Follow this phase order unless the user explicitly asks for a different sequence: fetch → analyze → fix → validate → push → reply → resolve → verify. Do not interleave GitHub thread mutations with code-editing retries.
1. Ask whether to use a dedicated git worktree
- Ask this before any other workflow questions.
- If yes, use/create the worktree and continue there.
2. Gather unresolved comments and create working tracker
- If a review URL with
#pullrequestreview-<id>was provided, parse owner, repo, PR number, and review id from it before fetching. - Copy or open `assets/review-resolution-checklist.md`—this becomes your working document. Fill in the context section and update the comment tracking table as you work through each thread.
- Fetch review threads for the PR and filter unresolved threads.
- If a specific review id or review URL is provided, limit to comments from that review.
- Within the targeted review, collect all comments associated with that review id (do not include replies from other reviews unless explicitly requested).
- Build a working list with: thread id, comment id, parent review id, file path, original comment body, and all subsequent replies in that thread (including contributor replies).
- Read the full reply chain for each thread — contributors may have added clarifications, constraints, or additional context that must be taken into account when deciding how to resolve the comment.
- Capture a baseline list of targeted thread ids and unresolved-thread count before any GitHub mutation.
3. Understand, research, and judge each comment
- Read the referenced file(s) and nearby logic.
- Decide whether the feedback is correct, partially correct, outdated, or incorrect against the current code, requirements, and surrounding context.
- Reviewers are not automatically correct; do not make code churn just to satisfy a comment that is stale or wrong.
- If the feedback is correct, verify root cause and identify the smallest safe fix.
- If the feedback is clearly incorrect or outdated, prepare a concise evidence-based reply instead of changing code unnecessarily.
- If uncertain, inspect adjacent tests/usages before editing.
- If doubt remains after local research, ask the user before making code changes or mutating that review thread.
4. Fix, check, commit (per comment)
- Apply focused code/doc changes only for comments you judged valid or partially valid.
- Run targeted checks first, then required repo checks.
- Create one commit per comment when practical.
- If two comments require one inseparable change, use one commit and map both comments to that commit in replies.
- For comments you decline, record the reasoning in the tracker so the eventual reply is explicit and auditable.
5. Push once after all fixes
- After all comment-related commits are created, push branch updates once.
6. Reply and resolve each review comment
- Before any thread mutation, prefer structured tooling in this order:
1. dedicated GitHub MCP review-thread reply/resolve tools exposed in the current client session, 2. the bundled GraphQL assets or bundled scripts/resolve-review-comments.sh, 3. never ad hoc temporary Python scripts, one-off batch helpers, or blind gh api retry loops.
- Prepare exactly one planned reply per targeted thread from the checklist/tracker before posting anything.
- Re-fetch the current thread state before retrying if a reply/resolve attempt errors, times out, or returns an uncertain result.
- For each thread, these two steps are mandatory and must happen together in order unless the thread is still uncertain and waiting for user input:
1. Post a reply on the thread: either describe what changed and include the commit hash(es), or explain why no code change was made because the comment is incorrect/outdated. 2. Resolve the thread immediately after the reply is posted — never before.
- Post at most one reply attempt per thread per run.
- If an equivalent agent-authored reply already exists, do not post another reply; reuse it and only resolve if the thread is still unresolved.
- If a different agent-authored reply already exists, stop and inspect manually rather than stacking another comment.
- Do not resolve a thread that is still uncertain; escalate it to the user first.
- Never resolve a thread without a reply. Never post a reply without then resolving the thread.
- Keep replies specific: name the file/line changed and the commit, not just "fixed".
7. Verify closure state
- Re-check review threads and confirm no targeted unresolved threads remain.
- Confirm the targeted unresolved-thread count dropped to zero and no duplicate agent replies were created during this run.
- Re-check latest CI status if the workflow expects green checks.
8. Ask whether to request a new review from the original review author
- After fixes are pushed and threads are resolved, ask if the user wants to request a new review from the author of the review comments.
- If yes, request review from that reviewer username and report that the request was sent.
9. Optional scripted execution
- Use
scripts/get-review-comments.shto fetch matching review comments (including sub-comments associated with the review id). - Results are limited to the first 100 review threads and first 100 comments per thread.
- Example test:
skills/.experimental/fusion-github-review-resolution/scripts/get-review-comments.sh --owner equinor --repo fusion-skills --pr 27 --review-id 3837647674- Use
--include-outdatedwhen you need comments from outdated matching threads. - Use
scripts/resolve-review-comments.shto reply+resolve matching threads with a dry-run-first duplicate-reply guard. - Keep default dry-run behavior; use
--applyonly after fixes are committed and pushed. - By default the script refuses to add another authenticated-user reply to a thread that already contains one; use
--allow-additional-replyonly after manual inspection. - Example dry-run:
skills/.experimental/fusion-github-review-resolution/scripts/resolve-review-comments.sh --owner equinor --repo fusion-skills --pr 27 --review-id 3837647674 --include-resolved- Example apply:
skills/.experimental/fusion-github-review-resolution/scripts/resolve-review-comments.sh --owner equinor --repo fusion-skills --pr 27 --review-id 3837647674 --apply --message "Addressed in <commit>: <what changed>."
Tooling map (MCP vs GraphQL)
Use GitHub MCP tools for high-level PR operations and any dedicated review-thread mutations the current client exposes. Use GraphQL for thread-level review operations when MCP coverage is missing.
| Workflow action | Preferred tool | Notes |
|---|---|---|
| Request reviewer / update PR metadata | mcp_github_update_pull_request | Works for collaborator reviewers and standard PR updates. |
| Create or submit PR review | mcp_github_pull_request_review_write | Handles pending review lifecycle actions. |
| Add general PR comment | mcp_github_add_issue_comment | Adds issue-style comment to PR conversation, not inline thread reply. |
| List review threads and comments | assets/pull-request-review-threads.graphql | Use with gh api graphql -f query=@assets/pull-request-review-threads.graphql for thread-level context. |
| Count unresolved threads for specific review id | assets/unresolved-thread-count-for-review.graphql | Post-process response (for example with jq) to filter by review id and unresolved state. |
| Reply to a review thread | Dedicated MCP review-thread reply tool, otherwise assets/add-pull-request-review-thread-reply.graphql | Prefer the MCP tool when available; otherwise use the bundled thread-scoped mutation instead of ad hoc scripts. |
| Resolve a review thread | Dedicated MCP review-thread resolve tool, otherwise assets/resolve-review-thread.graphql | Use the matching structured tool for the current client/session. ⚠️ GraphQL note: resolveReviewThread uses threadId, not pullRequestReviewThreadId. |
| List PR reviews (review URL/id lookup support) | assets/pull-request-reviews.graphql | Useful when starting from review URL context. |
Pro tip: See each .graphql file in assets for complete mutation/query syntax and parameter names.GraphQL cost awareness
Review-resolution workflows make multiple GraphQL mutation calls (reply + resolve per thread). Be conservative:
- Mutations cost 5 secondary-limit points each (vs 1 for read queries). Budget accordingly when processing many threads.
- Pause at least 1 second between consecutive mutation calls to avoid secondary rate limits.
- Keep
first/lastconnection arguments small (preferfirst: 100only when you need all threads in a single page). - If a secondary rate-limit error or
retry-afterheader is returned, stop processing and respect the indicated wait before retrying. - Always prefer a dedicated MCP review-thread tool over raw GraphQL when the client exposes one.
Token budget guidance
- Fetch the full thread list once and reuse it for all per-thread work; do not re-fetch threads between reply and resolve.
- Budget estimate: for N unresolved threads expect ~1 list call + N reply mutations + N resolve mutations = 1 + 2N calls. A 10-thread review costs ~21 calls.
- If the thread count exceeds 15, warn the user about rate-limit risk before starting mutations and offer to batch in smaller groups.
- Cache PR metadata (title, branch, CI status, changed files) from the first fetch and reuse it for commit messages and replies.
- Avoid redundant PR-level reads between steps; the data does not change within a single resolution run.
Expected output
Return a concise report containing:
- comments processed count,
- disposition summary (fixed, declined with rationale, escalated to user),
- files changed,
- commit list (hash + message),
- validation commands run and outcomes,
- confirmation of push,
- reply/resolve confirmation per thread,
- completed checklist location,
- any remaining unresolved threads or blockers.
Linked issue usage
When an issue is provided (for example equinor/fusion-core-tasks#432):
- mention the issue in progress/final summaries,
- keep implementation aligned with issue scope,
- avoid expanding to unrelated PR automation.
Safety & constraints
- This skill is mutation-capable. Repository-local workflow instructions take precedence over inline guidance when they conflict.
- Never expose secrets or tokens in logs/replies.
- Prefer argv-based process execution over shell-interpolated command strings.
- Keep diffs minimal and scoped to review feedback.
- Do not assume review feedback is correct; reason about it against the code and requirements first.
- If a comment remains ambiguous after research, ask the user instead of guessing.
- Do not resolve a thread without posting a concrete fix reply.
- Do not claim checks passed unless commands were actually run.
- Do not force-push; use regular commits and a single push after all fixes.
- If a comment is outdated but still unresolved, either:
- resolve with a clear explanation and commit reference, or
- leave unresolved and report why.
- In scripted mode, keep default dry-run behavior and require explicit
--applyfor mutations.
mutation AddPullRequestReviewThreadReply($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $threadId, body: $body }) {
comment {
url
}
}
}
query PullRequestReviewThreads($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
isOutdated
path
line
comments(first: 100) {
nodes {
databaseId
body
url
author {
login
}
createdAt
pullRequestReview {
databaseId
state
author {
login
}
}
}
}
}
}
}
}
}
query PullRequestReviews($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
reviews(first: 100) {
nodes {
databaseId
state
author {
login
}
submittedAt
url
}
}
}
}
}
mutation ResolveReviewThread($threadId: ID!) {
resolveReviewThread(input: { threadId: $threadId }) {
thread {
id
isResolved
}
}
}
Review Resolution Checklist
Use this checklist while processing unresolved review comments on a pull request.
Quick workflow
- [ ] Gather unresolved threads and map context
- [ ] Capture targeted unresolved-thread baseline
- [ ] Research and understand each comment
- [ ] Classify each comment (valid, partial, incorrect/outdated, uncertain)
- [ ] Apply focused fixes per comment
- [ ] Run validation checks
- [ ] Push all commits once
- [ ] Reply and resolve each thread
- [ ] Verify no unresolved threads remain
- [ ] Request re-review (optional)
Context
| Field | Value |
|---|---|
| Repository | |
| Pull request # | |
| Review ID (optional) | |
| Linked issue (optional) | |
| Base branch | |
| Working branch/worktree |
Validation commands
Targeted checks (run early, per-fix):
(paste commands here)Required full checks (run before push):
(paste commands here)Comment tracking
Note: Add more rows as needed. Each row = one review thread to resolve.
| Thread ID | File | Line(s) | Summary of feedback | Research notes | Disposition | Fix summary / rationale | Commit hash | Reply posted | Resolved |
|---|---|---|---|---|---|---|---|---|---|
| ☐ | ☐ | ||||||||
| ☐ | ☐ | ||||||||
| ☐ | ☐ |
Pre-mutation checks
- [ ] Baseline unresolved thread count captured
- [ ] Each targeted thread checked for existing agent replies
- [ ] Any uncertain comments escalated to the user before reply/resolve
- [ ] Reply text drafted once per thread before mutation
- [ ] Any failed/uncertain mutation attempts re-checked before retrying
Pre-push checklist
- [ ] Every tracked comment has a corresponding fix
- [ ] All fixes run and pass targeted checks
- [ ] Required full repository checks complete and pass
- [ ] Each fix has a commit with clear message
- [ ] Branch ready to push (no uncommitted changes)
Post-push / reply + resolve checklist
- [ ] Every comment has a reply with commit reference(s)
- [ ] Declined comments have evidence-based rationale replies
- [ ] Every reply is followed immediately by thread resolution
- [ ] No duplicate agent replies were posted during this run
- [ ] No unresolved threads remain (verify with GraphQL query)
- [ ] Any outdated threads addressed or documented
Final summary
Processed: ___ comments / ___ threads
Commits created:
Any remaining blockers:
query UnresolvedThreadCountForReview($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 1) {
nodes {
pullRequestReview {
databaseId
}
}
}
}
}
}
}
}
Changelog
0.1.7 - 2026-05-07
patch
- Drop articles, filler, hedging from SKILL.md activation body
0.1.6 - 2026-03-24
patch
- Add explicit "Repository-policy handoff" section that defers commit, validation, changeset, and PR rules to repo-local instructions
- Keeps the skill portable while ensuring repo-local policy takes precedence when present
Resolves equinor/fusion-core-tasks#581
0.1.5 - 2026-03-17
patch
fusion-issue-authoring: concrete session-cache flow for labels and assignee candidates; per-session budget tablefusion-issue-solving: expanded low-token strategy with session-cache references and budget awarenessfusion-github-review-resolution: token budget guidance for thread-heavy reviews; cache PR metadata oncefusion-issue-task-planning: session-cache delegation rules and batch-size warning for large task plansfusion-dependency-review: explicit data-reuse rules across parallel advisor fan-outfusion-discover-skills: tighter GraphQL budget and call-count cap for discovery sessions
resolves equinor/fusion-core-tasks#797
0.1.4 - 2026-03-11
patch
- #76 `3efc478` - Add GraphQL cost awareness section to review-resolution skill to enforce conservative mutation pacing and secondary rate-limit handling.
- Document per-mutation secondary cost (5 points) and per-query cost (1 point)
- Require at least 1-second pause between consecutive GraphQL mutation calls
- Require respect for
retry-afterheaders before retrying on rate-limit errors
resolves equinor/fusion-core-tasks#535
0.1.3 - 2026-03-06
patch
- #58 `22b4d66` - Tighten the experimental review-resolution workflow so agents follow a deterministic fetch → analyze → fix → validate → push → reply → resolve sequence and avoid ad hoc mutation scripts.
- Prefer structured review-thread tools when the client exposes them, otherwise use the bundled GraphQL assets or shell helper.
- Guard
resolve-review-comments.shagainst duplicate authenticated-user replies by default and use the thread-scoped GraphQL reply mutation. - Track mutation baselines and retry checks in the skill checklist so retries re-fetch state before posting again.
- Require the skill to judge whether review feedback is actually correct, reply with rationale when it is not, and ask the user when the comment remains ambiguous.
resolves equinor/fusion-skills#57
0.1.2 - 2026-03-05
patch
- #55 `2d346c8` - Add required ownership metadata (
metadata.owner,metadata.status) to all skills. Owner is set to@equinor/fusion-core(repository default) and status is set according to skill lifecycle (activefor production skills,experimentalfor early-stage skills). Sponsor metadata was considered but is not required for MVP.
resolves equinor/fusion-core-tasks#474
0.1.1 - 2026-03-03
patch
- #40 `cd68535` - Add one-operation-per-file GraphQL assets for review-thread workflows and document the MCP-vs-GraphQL tooling map in the experimental
fusion-github-review-resolutionskill.
0.1.0 - 2026-03-02
minor
- #33 `c8b513c` - Add experimental
fusion-github-review-resolutionskill with a deterministic workflow for unresolved PR review comments, including review-url trigger guidance, per-comment remediation process, and checklist support.
Add companion bash helpers:
skills/.experimental/fusion-github-review-resolution/scripts/get-review-comments.shskills/.experimental/fusion-github-review-resolution/scripts/resolve-review-comments.sh
The scripts support review-id scoped collection and safe dry-run-first resolution flows using gh.
resolves equinor/fusion-core-tasks#432
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
skills/.experimental/fusion-github-review-resolution/scripts/get-review-comments.sh \
--owner <owner> --repo <repo> --pr <number> --review-id <number> [--include-resolved] [--include-outdated]
Description:
Lists review-thread comments for a specific PR review id, including any sub-comments
that are associated with that review id. Default output is unresolved + non-outdated
threads only.
Note: Uses GraphQL queries limited to the first 100 review threads and first 100
comments per thread; results may be incomplete on very large pull requests.
Examples:
skills/.experimental/fusion-github-review-resolution/scripts/get-review-comments.sh \
--owner equinor --repo fusion-skills --pr 27 --review-id 3837647674
skills/.experimental/fusion-github-review-resolution/scripts/get-review-comments.sh \
--owner equinor --repo fusion-skills --pr 27 --review-id 3837647674 --include-outdated
EOF
}
OWNER=""
REPO=""
PR_NUMBER=""
REVIEW_ID=""
INCLUDE_RESOLVED="false"
INCLUDE_OUTDATED="false"
require_command() {
local name="$1"
local hint="$2"
if ! command -v "$name" >/dev/null 2>&1; then
echo "ERROR: '$name' is required. $hint" >&2
exit 1
fi
}
require_arg() {
local flag="$1"
local value="$2"
local hint="$3"
if [[ -z "$value" || "$value" == -* ]]; then
echo "ERROR: $flag requires $hint." >&2
usage
exit 1
fi
}
validate_owner() {
local value="$1"
if ! [[ "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,37}[A-Za-z0-9])?$ ]]; then
echo "ERROR: --owner must match GitHub owner naming rules (1-39 chars, alphanumeric or single hyphens, no leading/trailing hyphen)." >&2
exit 1
fi
if [[ "$value" =~ -- ]]; then
echo "ERROR: --owner cannot contain consecutive hyphens." >&2
exit 1
fi
}
validate_repo() {
local value="$1"
if ! [[ "$value" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "ERROR: --repo must contain only letters, numbers, dots, underscores, or hyphens." >&2
exit 1
fi
if (( ${#value} > 100 )); then
echo "ERROR: --repo must be 100 characters or fewer." >&2
exit 1
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
--owner)
require_arg "--owner" "${2:-}" "an owner value"
OWNER="$2"
shift 2
;;
--repo)
require_arg "--repo" "${2:-}" "a repository value"
REPO="$2"
shift 2
;;
--pr)
require_arg "--pr" "${2:-}" "a pull request number"
PR_NUMBER="$2"
shift 2
;;
--review-id)
require_arg "--review-id" "${2:-}" "a review id value"
REVIEW_ID="$2"
shift 2
;;
--include-resolved)
INCLUDE_RESOLVED="true"
shift
;;
--include-outdated)
INCLUDE_OUTDATED="true"
shift
;;
--help|-h)
usage
exit 0
;;
*)
echo "ERROR: Unknown argument: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$OWNER" || -z "$REPO" || -z "$PR_NUMBER" || -z "$REVIEW_ID" ]]; then
echo "ERROR: Missing required arguments." >&2
usage
exit 1
fi
validate_owner "$OWNER"
validate_repo "$REPO"
if ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
echo "ERROR: --pr must be a positive integer." >&2
exit 1
fi
if ! [[ "$REVIEW_ID" =~ ^[1-9][0-9]*$ ]]; then
echo "ERROR: --review-id must be a positive integer." >&2
exit 1
fi
require_command "gh" "Install GitHub CLI and authenticate with 'gh auth login'."
require_command "jq" "Install jq to parse JSON output."
if ! gh auth status >/dev/null 2>&1; then
echo "ERROR: GitHub CLI is not authenticated. Run 'gh auth login'." >&2
exit 1
fi
# shellcheck disable=SC2016
QUERY='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100){nodes{id isResolved isOutdated path line comments(first:100){nodes{databaseId body url author{login}createdAt pullRequestReview{databaseId state author{login}}}}}}}}}'
# shellcheck disable=SC2209
JSON_OUTPUT="$({
GH_PAGER=cat gh api graphql \
-f "query=$QUERY" \
-F "owner=$OWNER" \
-F "repo=$REPO" \
-F "number=$PR_NUMBER"
} )"
if [[ -z "$JSON_OUTPUT" ]]; then
echo "ERROR: Empty response from GitHub API." >&2
exit 1
fi
if printf '%s\n' "$JSON_OUTPUT" | jq -e '.errors | length > 0' >/dev/null 2>&1; then
echo "ERROR: GitHub GraphQL errors: $(printf '%s\n' "$JSON_OUTPUT" | jq -c '.errors')" >&2
exit 1
fi
if printf '%s\n' "$JSON_OUTPUT" | jq -e '.data == null' >/dev/null 2>&1; then
echo "ERROR: GitHub GraphQL response missing data." >&2
exit 1
fi
printf '%s\n' "$JSON_OUTPUT" | jq \
--argjson reviewId "$REVIEW_ID" \
--arg includeResolved "$INCLUDE_RESOLVED" \
--arg includeOutdated "$INCLUDE_OUTDATED" '
.data.repository.pullRequest.reviewThreads.nodes
| map(
. as $thread
| {
threadId: $thread.id,
isResolved: $thread.isResolved,
isOutdated: $thread.isOutdated,
path: $thread.path,
line: $thread.line,
comments: (
$thread.comments.nodes
| map(select(.pullRequestReview.databaseId == $reviewId))
)
}
| select(.comments | length > 0)
| if $includeResolved == "true" then . else select(.isResolved == false) end
| if $includeOutdated == "true" then . else select(.isOutdated == false) end
)
| if length == 0 then
{ summary: "No matching review comments found for the requested filters." }
else
{
summary: ("Found " + (length|tostring) + " matching thread(s)."),
threads: .
}
end
'
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
skills/.experimental/fusion-github-review-resolution/scripts/resolve-review-comments.sh \
--owner <owner> --repo <repo> --pr <number> --review-id <number> \
[--message <text> | --message-file <path>] [--apply] [--include-resolved] [--include-outdated] \
[--allow-additional-reply]
Description:
Resolves matching review threads for a given pull request review id.
Note: Uses GraphQL queries limited to the first 100 review threads and first 100
comments per thread; results may be incomplete on very large pull requests.
Safety:
- Default mode is dry-run and performs no GitHub mutations.
- --apply is required to post replies and resolve threads.
- --message or --message-file is required with --apply.
- By default the script fails closed when the authenticated user already replied on a target thread.
Use --allow-additional-reply only after manual inspection.
Examples:
Dry-run:
skills/.experimental/fusion-github-review-resolution/scripts/resolve-review-comments.sh \
--owner equinor --repo fusion-skills --pr 27 --review-id 3837647674 --include-resolved
Apply:
skills/.experimental/fusion-github-review-resolution/scripts/resolve-review-comments.sh \
--owner equinor --repo fusion-skills --pr 27 --review-id 3837647674 \
--apply --message "Addressed in <commit>: <what changed>."
EOF
}
OWNER=""
REPO=""
PR_NUMBER=""
REVIEW_ID=""
MESSAGE=""
APPLY="false"
INCLUDE_RESOLVED="false"
INCLUDE_OUTDATED="false"
MESSAGE_FROM_INLINE="false"
MESSAGE_FROM_FILE="false"
ALLOW_ADDITIONAL_REPLY="false"
VIEWER_LOGIN=""
require_command() {
local name="$1"
local hint="$2"
if ! command -v "$name" >/dev/null 2>&1; then
echo "ERROR: '$name' is required. $hint" >&2
exit 1
fi
}
require_arg() {
local flag="$1"
local value="$2"
local hint="$3"
if [[ -z "$value" || "$value" == -* ]]; then
echo "ERROR: $flag requires $hint." >&2
usage
exit 1
fi
}
validate_owner() {
local value="$1"
if ! [[ "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,37}[A-Za-z0-9])?$ ]]; then
echo "ERROR: --owner must match GitHub owner naming rules (1-39 chars, alphanumeric or single hyphens, no leading/trailing hyphen)." >&2
exit 1
fi
if [[ "$value" =~ -- ]]; then
echo "ERROR: --owner cannot contain consecutive hyphens." >&2
exit 1
fi
}
validate_repo() {
local value="$1"
if ! [[ "$value" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "ERROR: --repo must contain only letters, numbers, dots, underscores, or hyphens." >&2
exit 1
fi
if (( ${#value} > 100 )); then
echo "ERROR: --repo must be 100 characters or fewer." >&2
exit 1
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
--owner)
require_arg "--owner" "${2:-}" "an owner value"
OWNER="$2"
shift 2
;;
--repo)
require_arg "--repo" "${2:-}" "a repository value"
REPO="$2"
shift 2
;;
--pr)
require_arg "--pr" "${2:-}" "a pull request number"
PR_NUMBER="$2"
shift 2
;;
--review-id)
require_arg "--review-id" "${2:-}" "a review id value"
REVIEW_ID="$2"
shift 2
;;
--message)
require_arg "--message" "${2:-}" "a message value"
MESSAGE="$2"
MESSAGE_FROM_INLINE="true"
shift 2
;;
--message-file)
require_arg "--message-file" "${2:-}" "a path argument"
MESSAGE_FILE="$2"
if [[ ! -r "$MESSAGE_FILE" ]]; then
echo "ERROR: Message file '$MESSAGE_FILE' does not exist or is not readable" >&2
exit 1
fi
MESSAGE="$(cat "$MESSAGE_FILE")"
MESSAGE_FROM_FILE="true"
shift 2
;;
--apply)
APPLY="true"
shift
;;
--include-resolved)
INCLUDE_RESOLVED="true"
shift
;;
--include-outdated)
INCLUDE_OUTDATED="true"
shift
;;
--allow-additional-reply)
ALLOW_ADDITIONAL_REPLY="true"
shift
;;
--help|-h)
usage
exit 0
;;
*)
echo "ERROR: Unknown argument: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$OWNER" || -z "$REPO" || -z "$PR_NUMBER" || -z "$REVIEW_ID" ]]; then
echo "ERROR: Missing required arguments." >&2
usage
exit 1
fi
validate_owner "$OWNER"
validate_repo "$REPO"
if ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
echo "ERROR: --pr must be a positive integer." >&2
exit 1
fi
if ! [[ "$REVIEW_ID" =~ ^[1-9][0-9]*$ ]]; then
echo "ERROR: --review-id must be a positive integer." >&2
exit 1
fi
if [[ "$MESSAGE_FROM_INLINE" == "true" && "$MESSAGE_FROM_FILE" == "true" ]]; then
echo "ERROR: Use only one of --message or --message-file." >&2
exit 1
fi
if [[ "$APPLY" == "true" && ! "$MESSAGE" =~ [^[:space:]] ]]; then
echo "ERROR: --message or --message-file is required when --apply is set." >&2
exit 1
fi
require_command "gh" "Install GitHub CLI and authenticate with 'gh auth login'."
require_command "jq" "Install jq to parse JSON output."
if ! gh auth status >/dev/null 2>&1; then
echo "ERROR: GitHub CLI is not authenticated. Run 'gh auth login'." >&2
exit 1
fi
export GH_PAGER=cat
VIEWER_LOGIN="$(gh api user --jq '.login')"
if [[ -z "$VIEWER_LOGIN" ]]; then
echo "ERROR: Failed to determine the authenticated GitHub user." >&2
exit 1
fi
# shellcheck disable=SC2016
QUERY='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100){nodes{id isResolved isOutdated path line comments(first:100){nodes{databaseId url body author{login} pullRequestReview{databaseId}}}}}}}}'
# shellcheck disable=SC2209
JSON_OUTPUT="$({
GH_PAGER=cat gh api graphql \
-f "query=$QUERY" \
-F "owner=$OWNER" \
-F "repo=$REPO" \
-F "number=$PR_NUMBER"
} )"
if [[ -z "$JSON_OUTPUT" ]]; then
echo "ERROR: Empty response from GitHub API." >&2
exit 1
fi
if printf '%s\n' "$JSON_OUTPUT" | jq -e '.errors | length > 0' >/dev/null 2>&1; then
echo "ERROR: GitHub GraphQL errors: $(printf '%s\n' "$JSON_OUTPUT" | jq -c '.errors')" >&2
exit 1
fi
if printf '%s\n' "$JSON_OUTPUT" | jq -e '.data == null' >/dev/null 2>&1; then
echo "ERROR: GitHub GraphQL response missing data." >&2
exit 1
fi
TARGETS_JSON="$(printf '%s\n' "$JSON_OUTPUT" | jq -c \
--argjson reviewId "$REVIEW_ID" \
--arg includeResolved "$INCLUDE_RESOLVED" \
--arg includeOutdated "$INCLUDE_OUTDATED" \
--arg viewerLogin "$VIEWER_LOGIN" \
--arg message "$MESSAGE" '
.data.repository.pullRequest.reviewThreads.nodes
| map(
. as $thread
| {
threadId: $thread.id,
isResolved: $thread.isResolved,
isOutdated: $thread.isOutdated,
path: ($thread.path // "<unknown>"),
line: $thread.line,
commentIds: ($thread.comments.nodes | map(.databaseId)),
matchingReviewCommentIds: (
$thread.comments.nodes
| map(select(.pullRequestReview.databaseId == $reviewId) | .databaseId)
),
viewerReplyIds: (
$thread.comments.nodes
| map(
select(
(.author.login // "") == $viewerLogin
and ((.pullRequestReview.databaseId // 0) != $reviewId)
)
| .databaseId
)
),
hasMatchingViewerReply: (
$thread.comments.nodes
| map(
select(
(.author.login // "") == $viewerLogin
and ((.pullRequestReview.databaseId // 0) != $reviewId)
and ((.body // "") == $message)
)
)
| length > 0
)
}
| select(.matchingReviewCommentIds | length > 0)
| if $includeResolved == "true" then . else select(.isResolved == false) end
| if $includeOutdated == "true" then . else select(.isOutdated == false) end
)
')"
TARGET_COUNT="$(printf '%s\n' "$TARGETS_JSON" | jq 'length')"
if [[ "$TARGET_COUNT" == "0" ]]; then
echo "No matching review threads found for review $REVIEW_ID with current filters."
exit 0
fi
echo "Found $TARGET_COUNT matching thread(s):"
printf '%s\n' "$TARGETS_JSON" | jq -r '
to_entries[]
| "\(.key + 1). \(.value.threadId) \(.value.path)\(if .value.line then ":\(.value.line)" else "" end) comments=\(.value.matchingReviewCommentIds | map(tostring) | join(",")) viewerReplies=\(.value.viewerReplyIds | length)\(if .value.hasMatchingViewerReply then " [matching-reply]" else "" end)\(if .value.isResolved then " [resolved]" else "" end)\(if .value.isOutdated then " [outdated]" else "" end)"
'
if [[ "$APPLY" != "true" ]]; then
echo "Dry-run only. Re-run with --apply to post replies and resolve threads."
exit 0
fi
while IFS= read -r row; do
THREAD_ID="$(printf '%s\n' "$row" | jq -r '.threadId')"
IS_RESOLVED="$(printf '%s\n' "$row" | jq -r '.isResolved')"
VIEWER_REPLY_COUNT="$(printf '%s\n' "$row" | jq -r '.viewerReplyIds | length')"
HAS_MATCHING_VIEWER_REPLY="$(printf '%s\n' "$row" | jq -r '.hasMatchingViewerReply')"
if [[ "$IS_RESOLVED" == "true" ]]; then
echo "Thread $THREAD_ID is already resolved; skipping reply and resolve mutations."
continue
fi
# Guard against duplicate thread replies on retries. If the authenticated user already
# replied, fail closed unless the exact message is already present or the operator opts in.
if [[ "$HAS_MATCHING_VIEWER_REPLY" == "true" ]]; then
echo "Skipping reply for thread $THREAD_ID; identical reply already exists from $VIEWER_LOGIN."
elif [[ "$VIEWER_REPLY_COUNT" != "0" && "$ALLOW_ADDITIONAL_REPLY" != "true" ]]; then
echo "ERROR: Thread $THREAD_ID already has $VIEWER_REPLY_COUNT reply/replies from $VIEWER_LOGIN. Re-run with --allow-additional-reply only after manual inspection." >&2
exit 1
else
# Use the thread-scoped GraphQL mutation so replies stay attached to the review thread.
# shellcheck disable=SC2209,SC2016
ADD_REPLY_OUTPUT="$({
GH_PAGER=cat gh api graphql \
-f 'query=mutation($threadId:ID!,$body:String!){addPullRequestReviewThreadReply(input:{pullRequestReviewThreadId:$threadId,body:$body}){comment{url}}}' \
-F "threadId=$THREAD_ID" \
-f "body=$MESSAGE"
} )"
if [[ -z "$ADD_REPLY_OUTPUT" ]]; then
echo "ERROR: Empty reply mutation response for thread $THREAD_ID." >&2
exit 1
fi
if printf '%s\n' "$ADD_REPLY_OUTPUT" | jq -e '.errors | length > 0' >/dev/null 2>&1; then
echo "ERROR: Reply mutation errors for thread $THREAD_ID: $(printf '%s\n' "$ADD_REPLY_OUTPUT" | jq -c '.errors')" >&2
exit 1
fi
REPLY_URL="$(printf '%s\n' "$ADD_REPLY_OUTPUT" | jq -r '.data.addPullRequestReviewThreadReply.comment.url // empty')"
if [[ -z "$REPLY_URL" ]]; then
echo "ERROR: Reply mutation for thread $THREAD_ID did not return a comment URL." >&2
exit 1
fi
echo "Posted reply for thread $THREAD_ID: $REPLY_URL"
fi
# shellcheck disable=SC2209,SC2016
MUTATION_OUTPUT="$({
GH_PAGER=cat gh api graphql \
-f 'query=mutation($threadId:ID!){resolveReviewThread(input:{threadId:$threadId}){thread{id isResolved}}}' \
-F "threadId=$THREAD_ID"
} )"
if [[ -z "$MUTATION_OUTPUT" ]]; then
echo "ERROR: Empty mutation response for thread $THREAD_ID." >&2
exit 1
fi
if printf '%s\n' "$MUTATION_OUTPUT" | jq -e '.errors | length > 0' >/dev/null 2>&1; then
echo "ERROR: GraphQL mutation errors for thread $THREAD_ID: $(printf '%s\n' "$MUTATION_OUTPUT" | jq -c '.errors')" >&2
exit 1
fi
if ! printf '%s\n' "$MUTATION_OUTPUT" | jq -e '.data.resolveReviewThread.thread.isResolved == true' >/dev/null 2>&1; then
echo "ERROR: Thread $THREAD_ID was not resolved." >&2
exit 1
fi
echo "Resolved thread $THREAD_ID."
done < <(printf '%s\n' "$TARGETS_JSON" | jq -c '.[]')
echo "Completed: processed $TARGET_COUNT matching thread(s)."
Related skills
Forks & variants (1)
Fusion Github Review Resolution has 1 known copy in the catalog totaling 2 installs. They canonicalize to this original listing.
- equinor - 2 installs
How it compares
Pick fusion-github-review-resolution when you need programmatic thread resolution with GraphQL replies, not just listing open comments.
FAQ
Should I use a git worktree?
The skill asks this first. Use a worktree if you need isolation from your main branch; otherwise a direct checkout works. The skill supports both.
What if a review comment is incorrect or outdated?
The skill evaluates each comment against current code. If incorrect, it posts a reply explaining why no code change was made, with evidence, instead of creating churn.
Can I resolve threads without posting a reply?
No. The skill enforces that a reply is posted first, then the thread is resolved immediately after. Never one without the other.
Is Fusion Github Review Resolution safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.