AgentSkillsCN

handle-pr-feedback

获取并处理PR反馈(评审与评论),以便进行裁决与修复。在以下场景中使用此技能:(1) 获取PR评审评论;(2) 处理评审者的反馈;(3) 回复PR评论;(4) 规范来自不同来源的反馈;(5) 自动化PR反馈处理流程;(6) 向GitHub Copilot申请重新评审;或(7) 关注PR的新反馈。

SKILL.md
--- frontmatter
name: handle-pr-feedback
description: Fetch and process PR feedback (reviews and comments) for adjudication and remediation. Use this skill when (1) fetching PR review comments, (2) processing reviewer feedback, (3) replying to PR comments, (4) normalizing feedback from different sources, (5) automating PR feedback handling workflows, (6) requesting re-reviews from GitHub Copilot, or (7) watching PRs for new feedback.

Handle PR Feedback Skill

Provides deterministic operations for fetching, normalizing, replying to PR feedback, requesting reviews, and watching for new feedback.

Available Scripts

All scripts are located in .github/skills/handle-pr-feedback/scripts/

ScriptPurposeKey Use Cases
fetch-pr-feedback.pyFetch all PR feedbackGet reviews, inline comments, general comments
reply-to-comment.pyReply to PR commentsPost replies to inline/general comments
resolve-thread.pyResolve review threadsMark threads as resolved after fixes
request-review.pyRequest reviewerRequest Copilot or user re-review
watch-pr-feedback.pyWatch for new feedbackMonitor PR in feedback loops

IMPORTANT: Always use these existing scripts. Never create new scripts for these operations.

Prerequisites

  • GitHub CLI (gh) must be installed and authenticated
  • Python 3.9+ (available on macOS, Linux, Windows)
  • Repository access (public or authenticated for private repos)
  • For replying to comments: write access to the repository

Script Documentation

fetch-pr-feedback.py

Fetches all feedback from a PR (reviews, inline comments, general comments) in a normalized format.

Usage:

bash
python3 ./scripts/fetch-pr-feedback.py <PR_NUMBER> [REPO]
# or
./scripts/fetch-pr-feedback.py <PR_NUMBER> [REPO]

Arguments:

ArgumentRequiredDescription
PR_NUMBERYesPull request number
REPONoRepository in owner/repo format. Auto-detects if not provided.

Output JSON:

json
{
  "success": true,
  "prNumber": 123,
  "repo": "owner/repo",
  "feedbackItems": [
    {
      "id": "12345",
      "type": "review",
      "author": "reviewer",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-01T00:00:00Z",
      "state": "CHANGES_REQUESTED",
      "body": "Please address these issues...",
      "file": null,
      "line": null,
      "url": "https://github.com/..."
    },
    {
      "id": "23456",
      "type": "inline",
      "author": "reviewer",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-01T00:00:00Z",
      "state": null,
      "body": "This could cause a null pointer exception",
      "file": "src/handler.ts",
      "line": 42,
      "url": "https://github.com/..."
    },
    {
      "id": "34567",
      "type": "general",
      "author": "maintainer",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-01T00:00:00Z",
      "state": null,
      "body": "Overall looks good, just a few minor issues.",
      "file": null,
      "line": null,
      "url": "https://github.com/..."
    }
  ],
  "summary": {
    "totalItems": 3,
    "reviews": 1,
    "inlineComments": 1,
    "generalComments": 1,
    "authors": ["reviewer", "maintainer"]
  },
  "error": null
}

Feedback Types:

TypeDescriptionHas File/Line?
reviewPR review submission (APPROVE, REQUEST_CHANGES, COMMENT)No
inlineComment on specific line in diffYes
generalGeneral comment on the PR (not tied to code)No

Exit Codes:

CodeDescription
0Success
1Invalid arguments
2PR not found
3API error

reply-to-comment.py

Replies to an existing PR comment (inline or general).

Usage:

bash
python3 ./scripts/reply-to-comment.py <COMMENT_ID> <COMMENT_TYPE> [OPTIONS]
# or
./scripts/reply-to-comment.py <COMMENT_ID> <COMMENT_TYPE> [OPTIONS]

Arguments:

ArgumentRequiredDescription
COMMENT_IDYesID of the comment to reply to
COMMENT_TYPEYesType: inline or general

Options:

OptionDescription
--repo REPORepository in owner/repo format
--body TEXTReply body text
--body-file FILEPath to file with reply body (recommended for multi-line)
--pr-number NUMBERPR number (REQUIRED for both inline and general comments)
--dry-runOutput payload without posting

Output JSON:

json
{
  "success": true,
  "replyId": "45678",
  "url": "https://github.com/owner/repo/pull/123#discussion_r45678",
  "dryRun": false,
  "error": null
}

Exit Codes:

CodeDescription
0Success
1Invalid arguments
2API error

resolve-thread.py

Resolves or unresolves a PR review thread (marks conversation as resolved).

Usage:

bash
python3 ./scripts/resolve-thread.py <THREAD_ID> [OPTIONS]

Arguments:

ArgumentRequiredDescription
THREAD_IDYesGraphQL node ID of the thread (starts with PRRT_)

Options:

OptionDescription
--unresolveUnresolve the thread instead of resolving
--dry-runOutput payload without executing

Output JSON:

json
{
  "success": true,
  "threadId": "PRRT_kwDO...",
  "resolved": true,
  "dryRun": false,
  "error": null
}

Getting Thread IDs: Thread IDs are obtained via GraphQL query:

bash
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 }
        }
      }
    }
  }
' -f owner=OWNER -f repo=REPO -F pr=PR_NUMBER

Exit Codes:

CodeDescription
0Success
1Invalid arguments
2API error

request-review.py

Requests a review from a GitHub user (e.g., GitHub Copilot) on a PR.

Usage:

bash
python3 ./scripts/request-review.py <PR_NUMBER> <REVIEWER> [OPTIONS]
# or
./scripts/request-review.py <PR_NUMBER> <REVIEWER> [OPTIONS]

Arguments:

ArgumentRequiredDescription
PR_NUMBERYesPull request number
REVIEWERYesGitHub username to request review from (e.g., copilot)

Options:

OptionDescription
--repo REPORepository in owner/repo format
--dry-runOutput payload without posting

Output JSON:

json
{
  "success": true,
  "prNumber": 123,
  "reviewer": "copilot",
  "repo": "owner/repo",
  "requestedReviewers": ["copilot"],
  "dryRun": false,
  "error": null
}

Exit Codes:

CodeDescription
0Success
1Invalid arguments
2PR or reviewer not found
3API error

Common Use Cases:

bash
# Request GitHub Copilot to review
python3 ./scripts/request-review.py 123 copilot

# Request a team member
python3 ./scripts/request-review.py 123 team-member --repo owner/repo

# Dry run to preview
python3 ./scripts/request-review.py 123 copilot --dry-run

watch-pr-feedback.py

Watches a PR for new feedback with state tracking. Useful for implementing feedback loops.

Usage:

bash
# IMPORTANT: pr_number is the ONLY positional argument
# All other arguments are OPTIONS with flags
python3 ./scripts/watch-pr-feedback.py <PR_NUMBER> [OPTIONS]

# Examples:
python3 ./scripts/watch-pr-feedback.py 66
python3 ./scripts/watch-pr-feedback.py 66 --repo owner/repo --timeout 30

⚠️ Common Mistake: Do NOT pass repo as a positional argument!

  • ❌ WRONG: watch-pr-feedback.py 66 owner/repo
  • ✅ CORRECT: watch-pr-feedback.py 66 --repo owner/repo

Arguments:

ArgumentRequiredDescription
PR_NUMBERYesPull request number to watch (ONLY positional argument)

Options:

OptionDefaultDescription
--repo REPOauto-detectRepository in owner/repo format
--timeout MINUTES30Maximum watch duration
--interval SECONDS60Poll interval
--state-file FILEnoneState persistence file
--no-new-threshold N3Stop after N consecutive polls with no new feedback
--oncefalseCheck once and exit (no loop)
--detect-copilottrueEnable smart Copilot review detection
--no-detect-copilot-Disable Copilot review detection
--min-wait SECONDS60Minimum seconds before checking Copilot status

Copilot Review Detection: By default, the script monitors for Copilot review activity:

  • If Copilot is "reviewing": The script ignores the no-new-threshold and keeps waiting until Copilot completes or timeout is reached
  • If Copilot is "completed": Normal threshold behavior applies
  • If Copilot hasn't started after --min-wait seconds: Exits early with reason copilot_not_reviewing

This ensures you don't miss Copilot feedback when a review is actively in progress.

Output JSON:

json
{
  "success": true,
  "prNumber": 123,
  "repo": "owner/repo",
  "hasNewFeedback": true,
  "newFeedbackItems": [
    {
      "id": "12345",
      "type": "inline",
      "author": "copilot",
      "createdAt": "2026-01-06T12:00:00Z",
      "body": "Consider adding error handling here.",
      "file": "src/handler.ts",
      "line": 42
    }
  ],
  "summary": {
    "totalNew": 1,
    "pollCount": 5,
    "elapsedMinutes": 10.5,
    "exitReason": "new_feedback",
    "copilotStatus": "completed"
  },
  "state": {
    "startedAt": "2026-01-06T11:30:00Z",
    "lastProcessedAt": "2026-01-06T12:00:00Z",
    "processedFeedbackIds": ["12345", "23456"],
    "loopIterations": 5
  },
  "error": null
}

Exit Reasons:

ReasonDescription
new_feedbackNew feedback was detected
timeoutWatch timeout reached
no_new_thresholdN consecutive polls with no new feedback
single_pollUsed --once flag
copilot_not_reviewingCopilot not reviewing (after min-wait)

Exit Codes:

CodeDescription
0Success (with or without new feedback)
1Invalid arguments
2PR not found
3API error
4State file error (invalid JSON)

State File Format:

json
{
  "prNumber": 123,
  "repo": "owner/repo",
  "startedAt": "2026-01-06T11:30:00Z",
  "lastProcessedAt": "2026-01-06T12:00:00Z",
  "processedFeedbackIds": ["12345", "23456"],
  "loopIterations": 2
}

Common Use Cases:

bash
# Check once for new feedback
python3 ./scripts/watch-pr-feedback.py 123 --once

# Watch for 10 minutes with 30-second intervals
python3 ./scripts/watch-pr-feedback.py 123 --timeout 10 --interval 30

# Explicit repo (when not in git directory)
python3 ./scripts/watch-pr-feedback.py 123 --repo owner/repo

# Resume from previous state
python3 ./scripts/watch-pr-feedback.py 123 --state-file .tmp/watch-state.json

# Watch until 5 consecutive empty polls
python3 ./scripts/watch-pr-feedback.py 123 --no-new-threshold 5

# Disable Copilot detection (poll normally)
python3 ./scripts/watch-pr-feedback.py 123 --no-detect-copilot

Common Workflows

1. Fetch All PR Feedback

bash
python3 ./scripts/fetch-pr-feedback.py 123 > feedback.json

# Filter by author using Python (replace AUTHOR_NAME with the actual GitHub username)
python3 - << 'PY'
import json
from pathlib import Path

AUTHOR_NAME = "reviewer"  # Replace with the GitHub username to filter by
data = json.loads(Path("feedback.json").read_text())
filtered = [i for i in data.get("feedbackItems", []) if i.get("author") == AUTHOR_NAME]
print(json.dumps(filtered, indent=2))
PY

2. Reply to Inline Comment

bash
# Write reply to file to avoid shell escaping issues
cat > /tmp/reply.txt << 'EOF'
Thanks for catching this! Fixed in the latest commit.

The issue was due to missing null check. I've added:
```ts
if (!user) return null;

EOF

python3 ./scripts/reply-to-comment.py 12345 inline
--body-file /tmp/reply.txt

code

### 3. Reply to General Comment

```bash
cat > /tmp/reply.txt << 'EOF'
Thanks for the review! I've addressed all the feedback:

- Fixed null pointer in handler.ts
- Added missing tests
- Updated documentation
EOF

python3 ./scripts/reply-to-comment.py 34567 general \
  --pr-number 123 \
  --body-file /tmp/reply.txt

4. Dry Run Preview

bash
python3 ./scripts/reply-to-comment.py 12345 inline \
  --body "Acknowledged, will fix." \
  --dry-run

5. Request Copilot Review

bash
# Request GitHub Copilot to re-review after pushing fixes
python3 ./scripts/request-review.py 123 copilot

6. Watch Loop Workflow

bash
# Initialize state and check for existing feedback
python3 ./scripts/watch-pr-feedback.py 123 --once \
  --state-file .tmp/pr-state.json > initial.json

# Process any initial feedback...

# Start watch loop (polls every 60s for up to 30 minutes)
while true; do
  result=$(python3 ./scripts/watch-pr-feedback.py 123 \
    --timeout 30 --interval 60 \
    --state-file .tmp/pr-state.json)
  
  has_new=$(echo "$result" | python3 -c "import json,sys; print(json.load(sys.stdin).get('hasNewFeedback', False))")
  exit_reason=$(echo "$result" | python3 -c "import json,sys; print(json.load(sys.stdin).get('summary', {}).get('exitReason', ''))")
  
  if [ "$has_new" = "True" ]; then
    echo "New feedback detected, processing..."
    # Process new feedback items...
    # Push fixes...
    # Request re-review...
    python3 ./scripts/request-review.py 123 copilot
    continue
  fi
  
  if [ "$exit_reason" = "timeout" ] || [ "$exit_reason" = "no_new_threshold" ]; then
    echo "Watch loop complete: $exit_reason"
    break
  fi
done

Normalized Feedback Format

All feedback items are normalized to a consistent structure:

json
{
  "id": "string",           // Unique identifier
  "type": "string",         // "review" | "inline" | "general"
  "author": "string",       // GitHub username
  "createdAt": "string",    // ISO 8601 timestamp
  "updatedAt": "string",    // ISO 8601 timestamp
  "state": "string|null",   // For reviews: "APPROVED", "CHANGES_REQUESTED", "COMMENTED", "PENDING"
  "body": "string",         // Comment content
  "file": "string|null",    // File path (inline comments only)
  "line": "number|null",    // Line number (inline comments only)
  "url": "string"           // Direct link to comment
}

This format allows the agent to process all feedback uniformly regardless of source.


Error Handling

ErrorCauseSolution
gh not installedGitHub CLI missingInstall: brew install gh (macOS)
gh not authenticatedNot logged inRun: gh auth login
PR not foundInvalid PR numberVerify PR exists and you have access
API errorRate limits or permissionsCheck rate limits, verify access
Could not detect repositoryNot in git repoProvide --repo argument

Integration with Agents

This skill is used by:

  • pr-feedback-handler agent: Evaluates feedback and determines actions
  • handle-pr-feedback prompt: Orchestrates the feedback handling workflow

Agent Usage Example

markdown
1. Call `fetch-pr-feedback.py {PR_NUMBER}` to get all feedback
2. For each feedback item, determine judgment (VALID/PARTIALLY_VALID/MISGUIDED)
3. For items needing code changes, generate fixes
4. For items needing replies, generate reply content
5. Call `reply-to-comment.py` to post replies (with user confirmation)

Troubleshooting

"Not inside a git repository"

Run the commands from within a git repository, or provide the REPO argument explicitly.

"PR_NUMBER is required for general comment replies"

General comments are at the issue level, so the PR number is needed to post replies. Use --pr-number.

Reply not appearing in thread

For general comments, GitHub doesn't have true threading. The reply appears as a new comment referencing the original. For inline comments, replies appear in the proper thread.

Empty feedback items

If a PR has no reviews or comments yet, feedbackItems will be an empty array. The summary will show zero counts.