.agents/commands/comet/address-github-pr-comments.md
Command: cursor address-github-pr-comments
Given the current working branch (which must include an Opik ticket number), fetch the open GitHub PR for this branch in comet-ml/opik, collect comments/discussions that are not addressed yet, list them clearly, and propose how to address each one (or suggest closing when appropriate). Provide options to proceed with fixes or mark items as not needed.
This workflow will:
gh api (immediate for skips, deferred for fixes): threaded replies for inline comments, quote-replies on the issue thread for PR-level review bodiescomet-ml/opik
If unavailable, respond with: "This command needs GitHub MCP configured. Set MCP config/env, run
make cursor(Cursor) ormake claude(Claude CLI), then retry."
Stop here.
main<username>/OPIK-<ticket-number>-<kebab-short-description> and extract OPIK-<number>comet-ml/opikThere are three distinct categories of feedback to collect — missing any one of them silently drops reviewer comments on the floor:
pulls/{N}/comments)issues/{N}/comments)body (pulls/{N}/reviews), including reviews with zero inline commentsWhy category 3 matters: A reviewer can submit
state=CHANGES_REQUESTED(orCOMMENTED) with all their feedback in the review body and no inline comments. Such reviews do not appear in the GraphQLreviewThreadsquery (which only surfaces reviews containing inline comments) and they do not appear inpulls/{N}/commentsorissues/{N}/comments. They are only visible viapulls/{N}/reviews. Past incident: PR #6507 had aCHANGES_REQUESTEDreview whose body asked us to investigate a regression; it was missed on re-runs of this skill because the body wasn't surfaced.
Fetch all three sources: Use GitHub MCP / gh api for each. When using gh api for any list endpoint, always use --paginate to ensure all results are fetched:
# 1. Inline review comments (code-line-anchored)
gh api repos/comet-ml/opik/pulls/{pr_number}/comments --paginate
# 2. Issue thread comments (general PR comments)
gh api repos/comet-ml/opik/issues/{pr_number}/comments --paginate
# 3. PR-level reviews (includes review BODY text — required for category 3)
gh api repos/comet-ml/opik/pulls/{pr_number}/reviews --paginate
Why pagination is required: GitHub API returns 30 items per page by default. Opik PRs regularly exceed this — 17 CI test group comments + deployment bot comments + reviewer comments can push past 30 total. Without
--paginate, the agent silently gets only the first page and may miss real review feedback.
Determine pending/unaddressed items — evaluate each category separately:
Category 1 — Inline review comments (pulls/{N}/comments):
reviewThreads)/address-github-pr-comments markerCategory 2 — Issue thread comments (issues/{N}/comments):
/address-github-pr-comments marker)Category 3 — PR-level review bodies (pulls/{N}/reviews):
submitted_at descending and group by user.login. For each reviewer, keep only the newest review as the authoritative one — older reviews from the same reviewer are always considered superseded and are never pending, regardless of state. All checks below evaluate against this latest-per-reviewer set only.pull_request_review_id (present on each item in pulls/{N}/comments) to the count of inline comments. A COMMENTED review whose id has any associated inline comments is not Category 3 — its feedback lives in Category 1 (those inline comments) and is handled there. Apply this gate before evaluating pending status, so a review body is never double-counted.body AND meets one of:
state=CHANGES_REQUESTED, AND not later dismissed (no dismissed_at or state=DISMISSED). Per-reviewer dedup already handles supersession (e.g., a later APPROVED from the same reviewer wins because it's the newest).state=COMMENTED with non-empty body, AND the inline-comment association gate above shows zero inline comments for this review.id, AND no follow-up addressing it (no later quote-reply on the issue thread carrying the /address-github-pr-comments marker that quotes this review body — see "Idempotency on re-runs" below)reviewThreads, so they must be sourced from pulls/{N}/reviews directly/address-github-pr-comments AI marker whose quote block, normalized per the "Quote-body normalization" rule in Step 6, matches the same normalized form of this review body. Both quote generation and the addressed check must use the identical normalization function — otherwise a long body's truncated quote on first run won't match the full body on re-run, causing duplicate replies.All categories:
🤖 *Review posted via /review-github-pr*) are external review feedback even if posted from the same GitHub account. Never skip them based on the commenter's identity — only skip if the item already has the matching /address-github-pr-comments reply marker.If none pending across all three categories: Print: "No pending PR comments to address." and stop
Replies are posted differently depending on the feedback category. Inline review comments use threaded replies via in_reply_to; PR-level review bodies (which have no thread to attach to) use a quote-reply on the issue thread.
gh api repos/comet-ml/opik/pulls/{pr_number}/comments \
-f body="<reply text>" \
-F in_reply_to={comment_id}
PR-level review bodies (Category 3) have no thread to attach to (in_reply_to is not applicable). Acknowledge them by posting a quote-reply on the issue thread. The quoted body provides explicit linkage back to the review and makes the response visible in the conversation timeline; reviewer is notified via the standard PR-comment notification.
Both quote generation here AND the addressed-check in Step 3 (Category 3 idempotency) MUST use this exact same normalize(body) function — otherwise a long body's truncated quote on first run won't match the full body on a re-run and the skill will post duplicate "Fixed"/"Skipping" replies.
normalize(body):
> quote markers and one optional space (so previously quoted text inside the body doesn't double-prefix on re-quoting)… (single Unicode horizontal ellipsis, U+2026)For the on-the-wire quote block in the comment body, prefix each line of the normalized string with > (greater-than + single space). For the addressed-check, extract candidate quote blocks from existing issue-thread comments by stripping that same > prefix per line, then compare the resulting string to normalize(<review body>) — exact equality.
The 280-char limit is a hard contract, not a heuristic: changing it without updating both call sites silently breaks idempotency for previously-addressed long reviews.
gh api repos/comet-ml/opik/issues/{pr_number}/comments \
-f body="$(cat <<'EOF'
> @<reviewer-login> wrote in their review (<review-state>):
>
<each line of normalize(<review body>) prefixed with "> ">
<response: "Fixed in <sha> — ..." or "Skipping — ...">
🤖 *Reply posted via /address-github-pr-comments*
EOF
)"
The quoted, normalized body is the idempotency key for re-runs: on the next invocation, Step 3's Category 3 "addressed" check looks for an issue-thread comment carrying the /address-github-pr-comments marker whose extracted quote block equals normalize(<review body>). Always include the quote — it's the matching key.
All auto-posted replies must include a footer marker to distinguish them from human-written replies:
🤖 *Reply posted via /address-github-pr-comments*
When the user opts to skip an item, post the reply immediately. The body format is the same for inline and PR-level; only the endpoint differs (per the sections above):
Skipping — <brief rationale why this is not being addressed>
🤖 *Reply posted via /address-github-pr-comments*
For a PR-level review, wrap with the quote prefix shown in "PR-level Review Replies".
When the user opts to fix an item, defer the reply until the fix is pushed to the remote. This applies to both inline and PR-level items:
Track items that need deferred replies. For inline: comment ID + description. For PR-level: reviewer login + review id + raw review body (the quote is regenerated at post time via normalize(body) from the "Quote-body normalization" rule above) + description of fix.
After all fixes are applied, prompt the user to commit and push
Once git push completes, capture the commit SHA from the push output
Sync PR description: invoke the _pr-description-sync sub-skill (.agents/commands/comet/_pr-description-sync.md) with branch = git rev-parse --abbrev-ref HEAD and pr_number = {pr_number} from Step 2. This is the highest-drift moment in the PR lifecycle: review feedback often reshapes the API, method names, or events after the description was first written. The sub-skill is a no-op when the description is already in sync or when the user has opted out of refreshes for this repo.
Post deferred replies referencing the commit:
For inline (threaded):
Fixed in <commit_sha> — <brief description of what was changed>
🤖 *Reply posted via /address-github-pr-comments*
For PR-level (quote-reply on issue thread, using the "Quote-body normalization" rule and reply template above):
> @<reviewer-login> wrote in their review (<review-state>):
>
<each line of normalize(<review body>) prefixed with "> ">
Fixed in <commit_sha> — <brief description of what was changed>
🤖 *Reply posted via /address-github-pr-comments*
If the user declines to push immediately, remind them which items still need deferred replies and provide the reply commands they can run manually later.
After all replies are posted, offer to resolve the GitHub review threads that were addressed in this run.
Scope: This step applies only to inline review comments (Category 1). PR-level review bodies (Category 3) have no review thread to resolve — their acknowledgement is the quote-reply posted in Step 6, and they are not included here.
Use the GitHub GraphQL API to fetch review threads for the PR:
gh api graphql -f query='
query {
repository(owner: "comet-ml", name: "opik") {
pullRequest(number: PR_NUMBER) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 1) {
nodes {
databaseId
}
}
}
}
}
}
}
'
isResolved: false)databaseId from the thread's first comment against the comment IDs that received "Fixed" or "Skipping" repliesFor each matched thread, resolve via GraphQL mutation:
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "THREAD_NODE_ID"}) {
thread { isResolved }
}
}
'
The command is successful when:
comet-ml/opikpulls/{N}/comments (inline), issues/{N}/comments (issue thread), AND pulls/{N}/reviews (PR-level review bodies). Skipping any source silently drops feedback.gh api for posting. Inline comments → threaded reply via in_reply_to on pulls/{N}/comments. PR-level review bodies → quote-reply on issues/{N}/comments (no thread exists to attach to). GitHub MCP is used for reading; gh CLI is used for posting.🤖 *Reply posted via /address-github-pr-comments* footer — never omit itEnd Command