Task: review and address PR feedback
Goal
Read all reviewer comments on a pull request — including resolved/outdated ones and suppressed comments buried in Copilot review bodies (see Step 1a) — apply any necessary fixes to the code, post a reply to each comment explaining what was done, and resolve the threads where possible.
AI disclaimer
Every comment you post must end with the following disclaimer on its own line:
> *This comment was posted by an AI assistant (GitHub Copilot) on behalf of the pull request author.*
This applies to all PR comment types: inline replies, general PR comments, and review submission bodies.
Step 1 — Read the PR
# Get PR title, author, description
# Note: avoid --jq for complex filters — it silently exits 1 in this environment.
# Pipe to python3 instead for reliable parsing.
gh api repos/canonical/charm-integration-testing/pulls/<number> \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Title: {d[\"title\"]}\nAuthor: {d[\"user\"][\"login\"]}\nBranch: {d[\"head\"][\"ref\"]}\n\nBody:\n{d[\"body\"]}')"
# Read all inline review comments (--paginate ensures more than 100 comments are read)
gh api --paginate "repos/canonical/charm-integration-testing/pulls/<number>/comments?per_page=100" \
| python3 -c "
import sys, json
for c in json.load(sys.stdin):
print(f\"{c['path']}:{c.get('line','?')} [{c['user']['login']}] (id:{c['id']})\")
print(c['body'])
print('---')
"
# Read general (issue-style) comments
gh api --paginate "repos/canonical/charm-integration-testing/issues/<number>/comments?per_page=100" \
| python3 -c "
import sys, json
for c in json.load(sys.stdin):
print(f\"[{c['user']['login']}] (id:{c['id']})\")
print(c['body'])
print('---')
"
Read the review instructions file at .github/instructions/python-pr-review.instructions.md
before evaluating any Python changes.
Step 1a — Read Copilot review bodies, including suppressed comments
Copilot's automated reviews (copilot-pull-request-reviewer[bot]) post a review body with
a status header and, in <details> blocks, comments it decided not to post inline
("suppressed comments"). These are only visible in the review body, not in the
/pulls/<number>/comments inline-comments list, so they are easy to miss.
gh api --paginate "repos/canonical/charm-integration-testing/pulls/<number>/reviews?per_page=100" \
| python3 -c "
import sys, json, re
for r in json.load(sys.stdin):
if r['user']['login'] != 'copilot-pull-request-reviewer[bot]':
continue
body = r['body'] or ''
header = re.search(r'^###\s*(.+)', body, re.M)
print(f\"review id:{r['id']} status: {header.group(1) if header else '(no header)'}\")
m = re.search(r'Suppressed comments \((\d+)\)(.*?)(?:\n- \*\*Files reviewed|</details>|\Z)', body, re.S)
if m and int(m.group(1)) > 0:
print(' suppressed comments:')
print(m.group(2).strip())
print('---')
"
Status headers seen in this repo (and their meaning):
- 🟢 Approval recommended (or no header) — no changes requested. Note that the
script above prints its own literal
(no header)when the### ...regex finds nothing; that is the script's placeholder text, not a status Copilot itself emits — treat it the same as "Approval recommended". - 🟡 Changes recommended — Copilot posted at least one actionable inline comment;
these show up in the normal
/commentslist from Step 1. - 🔵 Needs a closer look — Copilot flagged a concern but suppressed the inline
comment (often because confidence was lower or it judged the point secondary).
The detail is only in the "Suppressed comments" block, not in
/comments.
Treat suppressed comments as real feedback to triage, not noise: read the quoted file/line and text, decide whether it's a valid issue, and if so fix it exactly as you would an inline comment. Do not skip a review just because its comments were suppressed or because the header is "Needs a closer look" rather than "Changes recommended".
Step 2 — Read the changed files
Check out the PR branch and read the relevant source files before making any changes:
# Find the branch name
gh api repos/canonical/charm-integration-testing/pulls/<number> \
| python3 -c "import sys,json; print(json.load(sys.stdin)['head']['ref'])"
git fetch origin <branch>
git checkout <branch>
Step 3 — Apply fixes
Make targeted fixes in the source files. Follow the repository coding conventions. Do not modify files outside the scope of the comments being addressed.
Important — credential-string redaction: The sandbox tools (view, cat,
and the edit tool's old_str matching) redact strings that look like
credentials (e.g. any value adjacent to the word password), replacing them
with ******. If the file you are editing contains such strings, the edit
tool's old_str will not match. Use python3 file I/O to read and rewrite
those files directly instead.
After editing, re-run any validation commands the user has previously requested in this session (e.g. lint, unit tests, integration tests) and fix any failures before committing. Only then commit:
git add <files>
git commit -m "fix: <short description>
<detail of what was fixed and why>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
Step 4 — Post replies
Reply to each inline comment individually. Do not post a single comprehensive PR comment in place of individual replies.
Reply to each inline comment using the gh api tool with stdin input:
gh api -X POST repos/canonical/charm-integration-testing/pulls/comments/<comment_id>/replies --input /dev/stdin <<EOF
{
"body": "<your reply>\n\n> *This comment was posted by an AI assistant (GitHub Copilot) on behalf of the pull request author.*"
}
EOF
Verify each reply by checking the returned id field is non-empty in the JSON output.
For general PR comments, curl works fine:
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/canonical/charm-integration-testing/issues/<number>/comments" \
-d '{"body": "<your reply>\n\n> *This comment was posted by an AI assistant (GitHub Copilot) on behalf of the pull request author.*"}'
Suppressed comments have no comment_id (they were never posted as a standalone
inline comment), so they cannot be replied to via the inline-replies endpoint. Address
each one with a single general PR comment instead, quoting the file/line so the reviewer
can match it to the review body:
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/canonical/charm-integration-testing/issues/<number>/comments" \
-d '{"body": "Re: suppressed comment on `<path>:<line>` (review <review_id>):\n\n<your reply>\n\n> *This comment was posted by an AI assistant (GitHub Copilot) on behalf of the pull request author.*"}'
Step 5 — Resolve threads (optional)
Resolve each review thread using the GraphQL API:
# First, get the node IDs for all open threads
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.github.com/graphql" \
-d '{
"query": "{ repository(owner: \"canonical\", name: \"charm-integration-testing\") { pullRequest(number: <number>) { reviewThreads(first: 20) { nodes { id isResolved comments(first: 1) { nodes { databaseId } } } } } } }"
}' | python3 -c "
import sys, json
d = json.load(sys.stdin)
threads = d['data']['repository']['pullRequest']['reviewThreads']['nodes']
for t in threads:
cid = t['comments']['nodes'][0]['databaseId'] if t['comments']['nodes'] else None
print(t['id'], t['isResolved'], cid)
"
# Resolve each open thread
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.github.com/graphql" \
-d '{"query": "mutation { resolveReviewThread(input: {threadId: \"<thread_node_id>\"}) { thread { id isResolved } } }"}'
Note: resolveReviewThread requires pull_requests=write on the token.
If it returns FORBIDDEN, the token lacks this scope — skip silently and note
that threads need manual resolution.
Step 6 — Push (if applicable)
Only push if you have confirmed write access to the branch:
git push origin <branch>
If push is blocked (SSH key missing, token scope insufficient), commit locally and report the commit SHA so a maintainer can cherry-pick or force-push.
Summary output
After completing all steps, report:
- Which comments were addressed and how, including any suppressed comments found in Copilot review bodies (list the review status header, e.g. "Needs a closer look")
- Which files were changed (with a one-line description per file)
- Which threads were resolved vs. left open (and why)
- The commit SHA (if a commit was made)