Created
September 26, 2025 20:48
-
-
Save mandelbro/66b738097c3f349b2bc224be653a3c00 to your computer and use it in GitHub Desktop.
Pull the current branch's unresolved pull request comments and display them in the terminal in JSON format
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -euo pipefail | |
| # Check required tools | |
| require_cmd() { | |
| if ! command -v "$1" >/dev/null 2>&1; then | |
| echo "❌ Required command '$1' not found. Please install it and retry." | |
| exit 1 | |
| fi | |
| } | |
| require_cmd gh | |
| require_cmd jq | |
| require_cmd git | |
| # Ensure GitHub CLI is authenticated | |
| if ! gh auth status >/dev/null 2>&1; then | |
| echo "❌ GitHub CLI is not authenticated. Run: gh auth login" | |
| exit 1 | |
| fi | |
| # Ensure we're inside a git repository | |
| if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then | |
| echo "❌ Not inside a git repository. Run this script from within the repo." | |
| exit 1 | |
| fi | |
| usage() { | |
| cat <<EOF | |
| Usage: $(basename "$0") [FLAGS] [PR_NUMBER] | |
| Retrieve unresolved PR review thread comments authored by a specific bot/user. | |
| If PR_NUMBER is not provided, the script will detect the current branch and use | |
| the PR associated with it. | |
| Note: This script only returns unresolved review thread comments, not resolved ones | |
| or top-level PR comments. | |
| Flags: | |
| -a, --all Return comments from all authors (no filtering) | |
| --author-filter <string> Case-insensitive substring to match author login (default: "claude") | |
| -h, --help Show this help text | |
| Examples: | |
| $(basename "$0") | |
| $(basename "$0") 123 | |
| $(basename "$0") 57 --author-filter copilot | |
| $(basename "$0") --author-filter copilot 57 | |
| $(basename "$0") --all | |
| EOF | |
| } | |
| # Usage: | |
| # Retrieve unresolved bot-authored review thread comments for a specific PR | |
| # ./gh-pr-unresolved-review-comments <pr_number> (optional) | |
| # | |
| # Example: | |
| # ./gh-pr-unresolved-review-comments 123 (optional) | |
| REPO_FULL=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo "") | |
| if [ -z "${REPO_FULL:-}" ]; then | |
| REPO_URL=$(git remote get-url origin 2>/dev/null || echo "") | |
| if [[ "$REPO_URL" =~ github.com[:/]([^/]+)/([^/.]+)(\.git)?$ ]]; then | |
| OWNER="${BASH_REMATCH[1]}" | |
| REPO="${BASH_REMATCH[2]}" | |
| else | |
| echo "❌ Could not determine GitHub owner/repo from local git remotes." >&2 | |
| exit 1 | |
| fi | |
| else | |
| OWNER="${REPO_FULL%/*}" | |
| REPO="${REPO_FULL#*/}" | |
| fi | |
| BRANCH=$(git branch --show-current) | |
| if [ -z "${BRANCH:-}" ]; then | |
| echo "❌ Could not determine current git branch." | |
| exit 1 | |
| fi | |
| # ------------------------------ | |
| # Parse flags/arguments (flags can appear before/after PR number) | |
| # ------------------------------ | |
| PR_NUMBER="" | |
| AUTHOR_FILTER="claude" | |
| FILTER_ALL="false" | |
| ARGS=("$@") | |
| IDX=0 | |
| while [ $IDX -lt ${#ARGS[@]} ]; do | |
| ARG="${ARGS[$IDX]}" | |
| case "$ARG" in | |
| -a|--all) | |
| FILTER_ALL="true" | |
| ;; | |
| --author|--author-filter) | |
| IDX=$((IDX+1)) | |
| if [ $IDX -ge ${#ARGS[@]} ]; then | |
| echo "❌ --author-filter requires a value" | |
| usage | |
| exit 1 | |
| fi | |
| AUTHOR_FILTER="${ARGS[$IDX]}" | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| --) | |
| ;; | |
| -*) | |
| echo "❌ Unknown option: $ARG" | |
| usage | |
| exit 1 | |
| ;; | |
| *) | |
| if [ -z "$PR_NUMBER" ]; then | |
| PR_NUMBER="$ARG" | |
| else | |
| echo "❌ Unexpected positional argument: $ARG" | |
| usage | |
| exit 1 | |
| fi | |
| ;; | |
| esac | |
| IDX=$((IDX+1)) | |
| done | |
| if [ "$FILTER_ALL" = "true" ]; then | |
| AUTHOR_FILTER_LC="" | |
| else | |
| AUTHOR_FILTER_LC=$(printf '%s' "$AUTHOR_FILTER" | tr '[:upper:]' '[:lower:]') | |
| fi | |
| # If PR number not provided, detect from current branch | |
| if [ -z "${PR_NUMBER:-}" ]; then | |
| PR_NUMBER=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' || echo "") | |
| if [ -z "${PR_NUMBER:-}" ] || [ "$PR_NUMBER" = "null" ]; then | |
| echo "❌ No open PR found for branch '$BRANCH'. Provide a PR number explicitly." | |
| echo " Example: $(basename "$0") 123" | |
| exit 1 | |
| fi | |
| echo "ℹ️ Using current branch '$BRANCH' (PR: $PR_NUMBER)" >&2 | |
| else | |
| if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "❌ Invalid PR number: '$PR_NUMBER'. Must be a positive integer." | |
| usage | |
| exit 1 | |
| fi | |
| fi | |
| REPO_ROOT=$(git rev-parse --show-toplevel) | |
| gh api graphql \ | |
| -f owner="$OWNER" \ | |
| -f repo="$REPO" \ | |
| -F pr="$PR_NUMBER" \ | |
| -f query=' | |
| query FetchReviewComments($owner: String!, $repo: String!, $pr: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $pr) { | |
| comments(first: 100) { | |
| nodes { | |
| id | |
| url | |
| bodyText | |
| createdAt | |
| author { login __typename } | |
| } | |
| } | |
| reviewThreads(first: 100) { | |
| nodes { | |
| isResolved | |
| path | |
| line | |
| startLine | |
| comments(first: 100) { | |
| nodes { | |
| id | |
| url | |
| bodyText | |
| createdAt | |
| author { login __typename } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ' | jq --arg root "$REPO_ROOT" --arg author "$AUTHOR_FILTER_LC" '[.data.repository.pullRequest.reviewThreads.nodes[] | |
| | select(.isResolved == false) | |
| | . as $thread | |
| | .comments.nodes[] | |
| | {id, | |
| url, | |
| bodyText, | |
| author: (.author.login // ""), | |
| authorType: (.author.__typename // ""), | |
| createdAt, | |
| path: $thread.path, | |
| line: ($thread.line // null), | |
| startLine: ($thread.startLine // null), | |
| localPath: ($root + "/" + $thread.path), | |
| source: "review_thread" | |
| }] | |
| | map(select(.author | ascii_downcase | contains($author)))' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment