Skip to content

Instantly share code, notes, and snippets.

@mandelbro
Created September 26, 2025 20:48
Show Gist options
  • Select an option

  • Save mandelbro/66b738097c3f349b2bc224be653a3c00 to your computer and use it in GitHub Desktop.

Select an option

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
#!/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