|
| 1 | +# How to Resolve GitHub Review Comments |
| 2 | + |
| 3 | +This guide documents how to mark review comment conversations as resolved after addressing reviewer feedback in pull requests. |
| 4 | + |
| 5 | +## Quick Reference |
| 6 | + |
| 7 | +```bash |
| 8 | +# 1. Get all review threads for PR #74 |
| 9 | +gh api graphql -f query=' |
| 10 | +query { |
| 11 | + repository(owner: "eman", name: "nwp500-python") { |
| 12 | + pullRequest(number: 74) { |
| 13 | + reviewThreads(first: 10) { |
| 14 | + nodes { |
| 15 | + id |
| 16 | + path |
| 17 | + line |
| 18 | + isResolved |
| 19 | + } |
| 20 | + } |
| 21 | + } |
| 22 | + } |
| 23 | +} |
| 24 | +' | jq '.data.repository.pullRequest.reviewThreads.nodes' |
| 25 | + |
| 26 | +# 2. Resolve a specific thread |
| 27 | +gh api graphql -f query=' |
| 28 | +mutation { |
| 29 | + resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) { |
| 30 | + thread { isResolved } |
| 31 | + } |
| 32 | +} |
| 33 | +' |
| 34 | +``` |
| 35 | + |
| 36 | +## Why Resolve Comments? |
| 37 | + |
| 38 | +- **Clarity**: Shows reviewers their feedback has been acted upon |
| 39 | +- **Workflow**: Prevents reviewers from re-reading already-addressed comments |
| 40 | +- **Signal Readiness**: Indicates PR is ready for another review pass |
| 41 | +- **Clean PR Interface**: Makes GitHub's PR conversation cleaner and easier to follow |
| 42 | + |
| 43 | +## Workflow: Address and Resolve |
| 44 | + |
| 45 | +### 1. Address the Comment |
| 46 | + |
| 47 | +Make the code changes requested by the reviewer: |
| 48 | +- Fix bugs |
| 49 | +- Update documentation |
| 50 | +- Refactor code |
| 51 | +- Add tests |
| 52 | + |
| 53 | +**Example:** |
| 54 | +```bash |
| 55 | +# Edit file based on comment |
| 56 | +nano src/nwp500/converters.py |
| 57 | + |
| 58 | +# Commit the change |
| 59 | +git add src/nwp500/converters.py |
| 60 | +git commit -m "Fix div_10 converter to handle string inputs" |
| 61 | + |
| 62 | +# Push to remote |
| 63 | +git push |
| 64 | +``` |
| 65 | + |
| 66 | +### 2. Verify Tests Pass |
| 67 | + |
| 68 | +Run all validation before marking comments as resolved: |
| 69 | + |
| 70 | +```bash |
| 71 | +# Run tests |
| 72 | +pytest --ignore=tests/test_mqtt_hypothesis.py |
| 73 | + |
| 74 | +# Run linting |
| 75 | +make ci-lint |
| 76 | + |
| 77 | +# Run type checking |
| 78 | +python3 -m mypy src/nwp500 --config-file pyproject.toml |
| 79 | +``` |
| 80 | + |
| 81 | +All must pass before proceeding. |
| 82 | + |
| 83 | +### 3. Get Review Thread IDs |
| 84 | + |
| 85 | +Query GraphQL to find threads that need resolving: |
| 86 | + |
| 87 | +```bash |
| 88 | +gh api graphql -f query=' |
| 89 | +query { |
| 90 | + repository(owner: "eman", name: "nwp500-python") { |
| 91 | + pullRequest(number: 74) { |
| 92 | + reviewThreads(first: 10) { |
| 93 | + nodes { |
| 94 | + id |
| 95 | + path |
| 96 | + line |
| 97 | + isResolved |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | +' | jq '.data.repository.pullRequest.reviewThreads.nodes' |
| 104 | +``` |
| 105 | + |
| 106 | +**Output example:** |
| 107 | +```json |
| 108 | +[ |
| 109 | + { |
| 110 | + "id": "PRRT_kwDOP_hNvM5ukIVT", |
| 111 | + "path": "src/nwp500/converters.py", |
| 112 | + "line": 125, |
| 113 | + "isResolved": false |
| 114 | + }, |
| 115 | + { |
| 116 | + "id": "PRRT_kwDOP_hNvM5ukIVo", |
| 117 | + "path": "tests/test_model_converters.py", |
| 118 | + "line": 212, |
| 119 | + "isResolved": false |
| 120 | + } |
| 121 | +] |
| 122 | +``` |
| 123 | + |
| 124 | +### 4. Identify Which Threads to Resolve |
| 125 | + |
| 126 | +Cross-reference the output with: |
| 127 | +1. Which file you modified |
| 128 | +2. Which line the comment was on (approximately) |
| 129 | +3. Whether `isResolved` is `false` |
| 130 | + |
| 131 | +### 5. Resolve the Threads |
| 132 | + |
| 133 | +For each thread you addressed: |
| 134 | + |
| 135 | +```bash |
| 136 | +gh api graphql -f query=' |
| 137 | +mutation { |
| 138 | + resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) { |
| 139 | + thread { |
| 140 | + isResolved |
| 141 | + } |
| 142 | + } |
| 143 | +} |
| 144 | +' |
| 145 | +``` |
| 146 | + |
| 147 | +Success response: |
| 148 | +```json |
| 149 | +{ |
| 150 | + "data": { |
| 151 | + "resolveReviewThread": { |
| 152 | + "thread": { |
| 153 | + "isResolved": true |
| 154 | + } |
| 155 | + } |
| 156 | + } |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +### 6. Verify All Are Resolved |
| 161 | + |
| 162 | +Re-run the query from Step 3 to confirm all addressed threads now show `"isResolved": true`: |
| 163 | + |
| 164 | +```bash |
| 165 | +gh api graphql -f query=' |
| 166 | +query { |
| 167 | + repository(owner: "eman", name: "nwp500-python") { |
| 168 | + pullRequest(number: 74) { |
| 169 | + reviewThreads(first: 10) { |
| 170 | + nodes { |
| 171 | + path |
| 172 | + isResolved |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | +} |
| 178 | +' | jq '.data.repository.pullRequest.reviewThreads.nodes' |
| 179 | +``` |
| 180 | + |
| 181 | +## Batch Resolving Multiple Threads |
| 182 | + |
| 183 | +If you have many threads to resolve, use a loop: |
| 184 | + |
| 185 | +```bash |
| 186 | +#!/bin/bash |
| 187 | + |
| 188 | +# Define thread IDs |
| 189 | +THREAD_IDS=( |
| 190 | + "PRRT_kwDOP_hNvM5ukIVT" |
| 191 | + "PRRT_kwDOP_hNvM5ukIVo" |
| 192 | + "PRRT_kwDOP_hNvM5ukIVx" |
| 193 | +) |
| 194 | + |
| 195 | +# Resolve each one |
| 196 | +for thread_id in "${THREAD_IDS[@]}"; do |
| 197 | + gh api graphql -f query=" |
| 198 | + mutation { |
| 199 | + resolveReviewThread(input: {threadId: \"$thread_id\"}) { |
| 200 | + thread { isResolved } |
| 201 | + } |
| 202 | + } |
| 203 | + " && echo "✓ $thread_id resolved" |
| 204 | +done |
| 205 | + |
| 206 | +echo "All threads resolved!" |
| 207 | +``` |
| 208 | + |
| 209 | +Or as a one-liner: |
| 210 | + |
| 211 | +```bash |
| 212 | +for id in PRRT_kwDOP_hNvM5ukIVT PRRT_kwDOP_hNvM5ukIVo PRRT_kwDOP_hNvM5ukIVx; do |
| 213 | + gh api graphql -f query="mutation { resolveReviewThread(input: {threadId: \"$id\"}) { thread { isResolved } } }" && echo "✓ $id resolved" |
| 214 | +done |
| 215 | +``` |
| 216 | + |
| 217 | +## Shell Function (Optional) |
| 218 | + |
| 219 | +Add this to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.): |
| 220 | + |
| 221 | +```bash |
| 222 | +# Resolve a single GitHub PR review thread |
| 223 | +resolve-pr-thread() { |
| 224 | + local thread_id="$1" |
| 225 | + if [[ -z "$thread_id" ]]; then |
| 226 | + echo "Usage: resolve-pr-thread THREAD_ID" |
| 227 | + echo "Example: resolve-pr-thread PRRT_kwDOP_hNvM5ukIVT" |
| 228 | + return 1 |
| 229 | + fi |
| 230 | + |
| 231 | + gh api graphql -f query=" |
| 232 | + mutation { |
| 233 | + resolveReviewThread(input: {threadId: \"$thread_id\"}) { |
| 234 | + thread { isResolved } |
| 235 | + } |
| 236 | + } |
| 237 | + " | jq '.data.resolveReviewThread.thread.isResolved' |
| 238 | +} |
| 239 | + |
| 240 | +# Get all unresolved threads for a PR |
| 241 | +pr-threads() { |
| 242 | + local pr_num="${1:?PR number required}" |
| 243 | + gh api graphql -f query=" |
| 244 | + query { |
| 245 | + repository(owner: \"eman\", name: \"nwp500-python\") { |
| 246 | + pullRequest(number: $pr_num) { |
| 247 | + reviewThreads(first: 10) { |
| 248 | + nodes { |
| 249 | + id |
| 250 | + path |
| 251 | + line |
| 252 | + isResolved |
| 253 | + } |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | + " | jq '.data.repository.pullRequest.reviewThreads.nodes' |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +Usage: |
| 263 | +```bash |
| 264 | +pr-threads 74 # List all threads for PR #74 |
| 265 | +resolve-pr-thread PRRT_kwDOP_hNvM5ukIVT # Resolve one thread |
| 266 | +``` |
| 267 | + |
| 268 | +## Special Cases |
| 269 | + |
| 270 | +### Unresolving a Thread |
| 271 | + |
| 272 | +If a reviewer asks for changes after you marked it resolved, unresolve it: |
| 273 | + |
| 274 | +```bash |
| 275 | +gh api graphql -f query=' |
| 276 | +mutation { |
| 277 | + unresolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) { |
| 278 | + thread { |
| 279 | + isResolved |
| 280 | + } |
| 281 | + } |
| 282 | +} |
| 283 | +' |
| 284 | +``` |
| 285 | + |
| 286 | +### Force-Pushed Commits |
| 287 | + |
| 288 | +When you amend and force-push commits: |
| 289 | +1. Old review threads remain resolvable |
| 290 | +2. Thread IDs don't change |
| 291 | +3. Line numbers may be different in new commits |
| 292 | +4. Comments still point to old code but threads can be resolved |
| 293 | +5. This is normal GitHub behavior - resolve all threads once your changes are complete |
| 294 | + |
| 295 | +### Multiple Changes to Same File |
| 296 | + |
| 297 | +If a reviewer left multiple comments on the same file and you addressed them all: |
| 298 | +1. Make all changes to the file |
| 299 | +2. Commit and push |
| 300 | +3. Get all thread IDs for that file |
| 301 | +4. Resolve each one individually |
| 302 | + |
| 303 | +## Troubleshooting |
| 304 | + |
| 305 | +### "Not Found" Error |
| 306 | + |
| 307 | +**Problem:** |
| 308 | +``` |
| 309 | +{ |
| 310 | + "message": "Not Found", |
| 311 | + "documentation_url": "https://docs.github.com/rest", |
| 312 | + "status": 404 |
| 313 | +} |
| 314 | +``` |
| 315 | + |
| 316 | +**Solutions:** |
| 317 | +- Verify PR number is correct |
| 318 | +- Check you have access to the repository |
| 319 | +- Verify `gh auth status` shows you're authenticated |
| 320 | +- Try running `gh auth login` again |
| 321 | + |
| 322 | +### No Threads Returned |
| 323 | + |
| 324 | +**Problem:** |
| 325 | +``` |
| 326 | +{ |
| 327 | + "data": { |
| 328 | + "repository": { |
| 329 | + "pullRequest": { |
| 330 | + "reviewThreads": { |
| 331 | + "nodes": [] |
| 332 | + } |
| 333 | + } |
| 334 | + } |
| 335 | + } |
| 336 | +} |
| 337 | +``` |
| 338 | + |
| 339 | +**Solutions:** |
| 340 | +- Verify PR number is correct |
| 341 | +- The PR may have only general comments (not review comments) |
| 342 | +- Try increasing `first: 10` to `first: 100` if there are many threads |
| 343 | +- Verify reviewers left inline code comments, not just PR comments |
| 344 | + |
| 345 | +### Thread Won't Resolve |
| 346 | + |
| 347 | +**Problem:** |
| 348 | +Mutation succeeds but `isResolved` still returns `false` on next check |
| 349 | + |
| 350 | +**Solutions:** |
| 351 | +- Wait a moment and query again (GitHub API may have delay) |
| 352 | +- Verify you're using the exact same thread ID |
| 353 | +- Check that you have write permissions on the repository |
| 354 | +- Try running `gh auth refresh` to refresh your token |
| 355 | + |
| 356 | +### Can't Find Thread ID for Comment I Fixed |
| 357 | + |
| 358 | +**Problem:** |
| 359 | +You fixed the code but can't find the matching thread ID |
| 360 | + |
| 361 | +**Possible Causes:** |
| 362 | +1. The comment was a general PR comment, not an inline code comment (can't be resolved) |
| 363 | +2. The thread was already resolved by someone else |
| 364 | +3. You're looking at the wrong PR number |
| 365 | +4. The comment was deleted by the reviewer |
| 366 | + |
| 367 | +**Solution:** |
| 368 | +Go to the PR on GitHub.com and manually verify the comment still exists and is an inline review comment (not a general comment). |
| 369 | + |
| 370 | +## References |
| 371 | + |
| 372 | +- [GitHub GraphQL API: resolveReviewThread](https://docs.github.com/en/graphql/reference/mutations#resolvereviewthread) |
| 373 | +- [GitHub GraphQL API: unresolveReviewThread](https://docs.github.com/en/graphql/reference/mutations#unresolvereviewthread) |
| 374 | +- [GitHub GraphQL API: Review Threads](https://docs.github.com/en/graphql/reference/objects#reviewthread) |
| 375 | +- [GitHub CLI: gh api](https://cli.github.com/manual/gh_api) |
| 376 | +- [GitHub: Review conversations](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#about-pull-request-reviews) |
0 commit comments