Skip to content

Interactive PR Reviews with GitHub Copilot in VS Code

Strict code review standards - like mandatory reviews and conversation resolution - are vital for preventing technical debt, but they often introduce major bottlenecks. The current GitHub Web and UI-focused review process necessitates not only distracting context switching but also significant manual labor. Constantly toggling between the browser to read comments and the IDE to fix code breaks flow and encourages developers to skim feedback rather than engaging with it deeply.

The solution isn't to lower standards, but to improve the workflow. This post demonstrates how to handle Pull Request reviews entirely within VS Code, using GitHub Copilot to interactively fetch comments, propose fixes, and resolve threads via the GitHub CLI. By keeping the entire cycle inside the editor, you can validate changes locally and resolve feedback faster - without ever opening a browser.

Why Code Reviews in VS Code Matter

While switching between browser and editor is distracting, the deeper problem lies in the limitations of web-based reviews.

When reviewing suggestions in a browser, developers are often limited to accepting them "as-is" - bundled and committed without local validation. You cannot easily run tests, check type safety, or iterate on the logic without manually copying code back to your local environment. This makes it difficult to improve upon a suggestion or validate its impact on the broader codebase.

Furthermore, the administrative burden of strict "Reply and Resolve" policies is significant. To comply with these standards, a developer must:

  1. Switch to the IDE to implement the fix and verify it.
  2. Commit the changes.
  3. Switch back to the browser to look up the comment thread.
  4. Draft a reply summarizing the change (often needing to hunt for the specific commit hash for traceability).
  5. Manually mark the thread as resolved.

Developers need a workflow that doesn't just keep them in VS Code, but brings the entire lifecycle of assessment, iteration, and documented resolution directly into the workspace. By automating the summaries and resolution steps, you can remove the manual labor while achieving better traceability and accountability.

Automating Code Reviews with Copilot and VS Code

The goal is a workflow that keeps you in the IDE throughout the entire review process. Here is an effective setup:

How It Works

  1. Retrieve Comments: Copilot fetches all inline review comments using the GraphQL API, presenting them in a structured summary with file paths and line numbers.
  2. Iterate Comments One by One: You work through the feedback sequentially.
    • Visual Context: Copilot shows you the exact code in question.
    • Interactive Discussion: You discuss the validity of the comment.
    • Proposed Solution: Copilot suggests a specific code fix.
  3. Approve and Implement: Once you approve the fix (and only then), Copilot applies the changes to your local files.
  4. Automated Reply & Resolution: Copilot commits the change, replies to the comment thread with the commit hash ("Fixed in commit abc1234"), and marks the thread as resolved in GitHub.

Tool Selection: GitHub CLI vs Pull Request Extension vs MCP Server

This workflow leverages the GitHub CLI (gh) and GraphQL API. While other tools offer GitHub integration, they typically lack the necessary mutation capabilities to programmatically resolve review threads - a critical requirement for fully automating this cycle:

  • GitHub Pull Request Extension for VS Code: Excellent for reviewing diffs, but does not expose an API for Copilot to programmatically resolve threads or navigate the "reply-and-resolve" loop autonomously.
  • GitHub MCP Server: Provides useful context gathering but currently lacks the mutation capabilities required to mark conversation threads as resolved via the API.

The GitHub CLI + GraphQL combination proved to be the only robust method to fetch thread IDs and programmatically execute the resolveReviewThread mutation, establishing the "resolve" step as an automated part of the coding cycle rather than a manual administrative chore. Additionally, using gh allows reusing the developer's existing authentication seamlessly, eliminating the need to configure extra tokens or expose credentials directly to Copilot agents.

Key Principles for Success

To make this workflow effective, we recommend adhering to a few core guidelines in the Copilot instructions:

  • Interactive & Collaborative: Copilot never acts autonomously. It proposes a solution and waits for your explicit "okay" before changing code or updating GitHub.
  • Traceability: Every reply includes the specific commit hash where the fix resides.
  • Micro-Focus: We resolve one comment completely before moving to the next, preventing cognitive overload.

Important

One technical hurdle involves the GitHub CLI's default pager (see gh config: pager), which can swallow output during automation. We solve this by prepending GH_PAGER=cat to our API calls. This ensures output is captured correctly without modifying the user's global gh configuration, preserving their preferred pager for normal CLI use.

Getting Started

To adopt this workflow, simply copy the instructions in the appendix below into your Copilot custom instructions (or save them as a reference file in your workspace). These instructions provide Copilot with the exact GraphQL queries and process steps needed to manage PR reviews interactively.

Appendix: Copilot Instructions

## Pull Request Review Workflow

### 1. Accessing GitHub Copilot Review Comments

When asked to review PR comments, use the GitHub GraphQL API to retrieve inline review comments:

```bash
# List all review threads and comments on a PR using GraphQL
# Note: Set GH_PAGER=cat to prevent output from opening in pager (alternate buffer)
GH_PAGER=cat gh api graphql -f query='
query($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
    pullRequest(number: $prNumber) {
    reviewThreads(first: 100) {
        nodes {
        id
        isResolved
        isOutdated
        isCollapsed
        comments(first: 50) {
            nodes {
            id
            body
            path
            position
            line
            startLine
            author {
                login
            }
            createdAt
            }
        }
        }
    }
    }
}
}' -f owner='{OWNER}' -f repo='{REPO}' -F prNumber={PR_NUMBER}
```

**Note:** GitHub Copilot review comments are inline code review comments attached to specific lines in the "Files changed" tab. They are NOT visible in the PR timeline via `github-pull-request_openPullRequest` tool - you must use the GraphQL API to access review threads with their resolution status.

**Pager Issue:** The `GH_PAGER=cat` prefix prevents `gh` from opening results in a pager (like `less`), which would use the terminal's "alternate buffer" and make output unavailable for processing. This is crucial when piping results or capturing output.

### 2. Interactive PR Comment Resolution Process

When the user requests review of PR comments, follow this structured workflow:

1. **Retrieve Comments**: Use the GraphQL API (Section 1) to fetch all inline review comments
2. **Present Summary**: Show all comments with file paths, line numbers, and descriptions
3. **Iterate Through Each Comment**:
- Present the comment and its context
- **Assess validity**: Explain whether the comment makes sense and why
- **Propose solution**: If valid, suggest a specific fix with code changes
- **Wait for approval**: Do NOT implement until the user explicitly approves with "okay" or similar confirmation
- **Implement**: Once approved, make the changes
- **Move to next**: Only proceed to the next comment after completing the current one
4. **Track Progress**: Keep track of which comments have been addressed
5. **Complete Review**: Confirm when all comments have been resolved

**Key Principles**:
- Work on ONE comment at a time
- Always wait for user approval before implementing
- Be interactive and collaborative, not autonomous
- Provide clear explanations for each assessment
- Propose specific, actionable solutions with code examples

### 3. Replying to PR Comments After Fixes

After addressing a PR comment with code changes, reply to the comment thread to document the resolution:

#### Process:

1. **Always ask for the commit hash** if changes were made:
```
"What's the commit hash for these changes? I'll use it in the reply to the PR comment."
```

2. **Reply to the comment thread using GraphQL:**
```bash
GH_PAGER=cat gh api graphql -f query='
mutation($threadId: ID!, $body: String!) {
    addPullRequestReviewThreadReply(input: {
    pullRequestReviewThreadId: $threadId
    body: $body
    }) {
    comment {
        id
        body
        createdAt
        author {
        login
        }
    }
    }
}' -f threadId="{THREAD_NODE_ID}" -f body="Fixed in {COMMIT_HASH}.

Brief description of how the issue was addressed."
```

3. **Verify the reply was created**:
```bash
GH_PAGER=cat gh api graphql -f query='query($owner: String!, $repo: String!, $prNumber: Int!) {
    repository(owner: $owner, name: $repo) {
    pullRequest(number: $prNumber) {
        reviewThreads(first: 100) {
        nodes {
            id
            comments(first: 50) {
            nodes { id author { login } body createdAt }
            }
        }
        }
    }
    }
}' -f owner='{OWNER}' -f repo='{REPO}' -F prNumber={PR_NUMBER} \
        --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.id == "{THREAD_NODE_ID}") | .comments.nodes | length'

#### Reply Template:

```
Fixed in {COMMIT_HASH}.

We've addressed this by:
1. [Specific change 1]
2. [Specific change 2]
3. [Additional improvements if any]

[Optional: Brief explanation of the solution approach]
```

#### Important Notes:

- **DO NOT** try to edit Copilot's original comment - create a reply instead
- **DO NOT** use `gh pr review --comment` as it creates a new top-level comment, not a reply to a thread
- **DO NOT** include PR comment numbers in commit messages
- **ALWAYS** include the commit hash in the reply for traceability
- **ALWAYS** use GraphQL for PR review operations - it provides thread resolution status and better structure
- **Thread IDs**: Use thread node IDs (format: `PRRT_...`) from Section 1 query, not individual comment IDs
- **Resolution**: Comment threads CAN be marked as resolved programmatically via GraphQL API (see Section 4 below)

#### Example Workflow:

```bash
# 1. User: "I've committed the fix as abc123f, please reply to thread PRRT_kwDOLJ_mhs5ti05M"

# 2. Reply to thread using GraphQL
GH_PAGER=cat gh api graphql -f query='
mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
    pullRequestReviewThreadId: $threadId
    body: $body
}) {
    comment {
    id
    body
    createdAt
    }
}
}' -f threadId="PRRT_kwDOLJ_mhs5ti05M" -f body="Fixed in abc123f.

We've addressed this by:
1. Adding proper error handling
2. Creating utility function for DRY principle

This ensures consistent error handling across all operations."

# 3. Verify with GraphQL
GH_PAGER=cat gh api graphql -f query='query($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
    pullRequest(number: $prNumber) {
    reviewThreads(first: 100) {
        nodes {
        id
        comments(first: 50) { nodes { id author { login } createdAt } }
        }
    }
    }
}
}' -f owner='{OWNER}' -f repo='{REPO}' -F prNumber={PR_NUMBER} \
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.id == "PRRT_kwDOLJ_mhs5ti05M") | .comments.nodes | length'
```

### 4. Resolving PR Comment Threads

After addressing all comments in a review thread, mark the thread as resolved using the GraphQL API:

```bash
# Resolve a review thread
GH_PAGER=cat gh api graphql -f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
    thread {
    id
    isResolved
    }
}
}' -f threadId="{THREAD_NODE_ID}"

# Unresolve a review thread if needed
GH_PAGER=cat gh api graphql -f query='
mutation($threadId: ID!) {
unresolveReviewThread(input: {threadId: $threadId}) {
    thread {
    id
    isResolved
    }
}
}' -f threadId="{THREAD_NODE_ID}"
```

**Getting Thread IDs:**
Thread node IDs are returned in the GraphQL query from Section 1 (look for `reviewThreads.nodes[].id`). Each thread groups logically related review comments together.

**When to resolve:**
- After replying with fixes and getting confirmation
- When all actionable items in the thread are complete
- As part of the interactive PR review workflow

**Note:** GraphQL requires node IDs (format: `PRRT_...`) rather than numeric IDs. Always fetch the thread ID from the review threads query first.