99PR_NUMBER=$1
1010BOT_LOGIN_GRAPHQL=" chatgpt-codex-connector"
1111
12+ if ! [[ " $PR_NUMBER " =~ ^[0-9]+$ ]]; then
13+ echo " β PR number must be numeric. Got: '$PR_NUMBER '"
14+ exit 1
15+ fi
16+
1217echo " Checking for unresolved Codex comments in PR #${PR_NUMBER} ..."
1318
14- # Use GraphQL to get all comments (including minimized status)
15- # shellcheck disable=SC2016 # Single quotes are intentional - this is a GraphQL query, not shell expansion
16- GRAPHQL_QUERY=' query($owner: String!, $repo: String!, $pr: Int!) {
19+ REPO_INFO=$( gh repo view --json owner,name --jq ' {owner: .owner.login, name: .name}' )
20+ OWNER=$( echo " $REPO_INFO " | jq -r ' .owner' )
21+ REPO=$( echo " $REPO_INFO " | jq -r ' .name' )
22+
23+ # Depot runners sometimes hit transient network timeouts to api.github.com.
24+ # Retry the GraphQL request a few times before failing the required check.
25+ MAX_ATTEMPTS=5
26+ BACKOFF_SECS=2
27+
28+ # shellcheck disable=SC2016 # Single quotes are intentional - these are GraphQL queries.
29+ COMMENTS_QUERY=' query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
1730 repository(owner: $owner, name: $repo) {
1831 pullRequest(number: $pr) {
19- comments(first: 100) {
32+ comments(first: 100, after: $cursor) {
33+ pageInfo {
34+ hasNextPage
35+ endCursor
36+ }
2037 nodes {
2138 id
2239 author { login }
@@ -25,7 +42,19 @@ GRAPHQL_QUERY='query($owner: String!, $repo: String!, $pr: Int!) {
2542 isMinimized
2643 }
2744 }
28- reviewThreads(first: 100) {
45+ }
46+ }
47+ }'
48+
49+ # shellcheck disable=SC2016 # Single quotes are intentional - these are GraphQL queries.
50+ THREADS_QUERY=' query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
51+ repository(owner: $owner, name: $repo) {
52+ pullRequest(number: $pr) {
53+ reviewThreads(first: 100, after: $cursor) {
54+ pageInfo {
55+ hasNextPage
56+ endCursor
57+ }
2958 nodes {
3059 id
3160 isResolved
@@ -45,50 +74,111 @@ GRAPHQL_QUERY='query($owner: String!, $repo: String!, $pr: Int!) {
4574 }
4675}'
4776
48- REPO_INFO=$( gh repo view --json owner,name --jq ' {owner: .owner.login, name: .name}' )
49- OWNER=$( echo " $REPO_INFO " | jq -r ' .owner' )
50- REPO=$( echo " $REPO_INFO " | jq -r ' .name' )
77+ fetch_graphql_with_retry () {
78+ local query=" $1 "
79+ shift
80+
81+ local attempt
82+ local backoff
83+ backoff=" $BACKOFF_SECS "
84+
85+ for (( attempt = 1 ; attempt <= MAX_ATTEMPTS; attempt++ )) ; do
86+ if gh api graphql \
87+ -f query=" $query " \
88+ -F owner=" $OWNER " \
89+ -F repo=" $REPO " \
90+ -F pr=" $PR_NUMBER " \
91+ " $@ " ; then
92+ return 0
93+ fi
94+
95+ if [ " $attempt " -eq " $MAX_ATTEMPTS " ]; then
96+ echo " β GraphQL query failed after ${MAX_ATTEMPTS} attempts"
97+ return 1
98+ fi
99+
100+ echo " β οΈ GraphQL query failed (attempt ${attempt} /${MAX_ATTEMPTS} ); retrying in ${backoff} s..."
101+ sleep " $backoff "
102+ backoff=$(( backoff * 2 ))
103+ done
104+ }
105+
106+ COMMENTS_CURSOR=" "
107+ ALL_COMMENTS=' []'
108+
109+ while true ; do
110+ if [ -n " $COMMENTS_CURSOR " ]; then
111+ COMMENTS_RESULT=$( fetch_graphql_with_retry " $COMMENTS_QUERY " -F cursor=" $COMMENTS_CURSOR " )
112+ else
113+ COMMENTS_RESULT=$( fetch_graphql_with_retry " $COMMENTS_QUERY " )
114+ fi
51115
52- # Depot runners sometimes hit transient network timeouts to api.github.com.
53- # Retry the GraphQL request a few times before failing the required check.
54- MAX_ATTEMPTS=5
55- BACKOFF_SECS=2
116+ if [ " $( echo " $COMMENTS_RESULT " | jq -r ' .data.repository.pullRequest == null' ) " = " true" ]; then
117+ echo " β PR #${PR_NUMBER} does not exist in ${OWNER} /${REPO} ."
118+ exit 1
119+ fi
120+
121+ PAGE_COMMENTS=$( echo " $COMMENTS_RESULT " | jq ' .data.repository.pullRequest.comments.nodes' )
122+ ALL_COMMENTS=$( jq -cn --argjson all " $ALL_COMMENTS " --argjson page " $PAGE_COMMENTS " ' $all + $page' )
56123
57- for (( attempt = 1 ; attempt <= MAX_ATTEMPTS; attempt++ )) ; do
58- if RESULT=$( gh api graphql \
59- -f query=" $GRAPHQL_QUERY " \
60- -F owner=" $OWNER " \
61- -F repo=" $REPO " \
62- -F pr=" $PR_NUMBER " ) ; then
124+ HAS_NEXT=$( echo " $COMMENTS_RESULT " | jq -r ' .data.repository.pullRequest.comments.pageInfo.hasNextPage' )
125+ if [ " $HAS_NEXT " != " true" ]; then
63126 break
64127 fi
65128
66- if [ " $attempt " -eq " $MAX_ATTEMPTS " ]; then
67- echo " β GraphQL query failed after ${MAX_ATTEMPTS} attempts"
129+ COMMENTS_CURSOR=$( echo " $COMMENTS_RESULT " | jq -r ' .data.repository.pullRequest.comments.pageInfo.endCursor' )
130+ if [ -z " $COMMENTS_CURSOR " ] || [ " $COMMENTS_CURSOR " = " null" ]; then
131+ echo " β Assertion failed: comments pagination cursor missing while hasNextPage=true"
132+ exit 1
133+ fi
134+ done
135+
136+ THREADS_CURSOR=" "
137+ ALL_THREADS=' []'
138+
139+ while true ; do
140+ if [ -n " $THREADS_CURSOR " ]; then
141+ THREADS_RESULT=$( fetch_graphql_with_retry " $THREADS_QUERY " -F cursor=" $THREADS_CURSOR " )
142+ else
143+ THREADS_RESULT=$( fetch_graphql_with_retry " $THREADS_QUERY " )
144+ fi
145+
146+ if [ " $( echo " $THREADS_RESULT " | jq -r ' .data.repository.pullRequest == null' ) " = " true" ]; then
147+ echo " β PR #${PR_NUMBER} does not exist in ${OWNER} /${REPO} ."
68148 exit 1
69149 fi
70150
71- echo " β οΈ GraphQL query failed (attempt ${attempt} /${MAX_ATTEMPTS} ); retrying in ${BACKOFF_SECS} s..."
72- sleep " $BACKOFF_SECS "
73- BACKOFF_SECS=$(( BACKOFF_SECS * 2 ))
151+ PAGE_THREADS=$( echo " $THREADS_RESULT " | jq ' .data.repository.pullRequest.reviewThreads.nodes' )
152+ ALL_THREADS=$( jq -cn --argjson all " $ALL_THREADS " --argjson page " $PAGE_THREADS " ' $all + $page' )
153+
154+ HAS_NEXT=$( echo " $THREADS_RESULT " | jq -r ' .data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage' )
155+ if [ " $HAS_NEXT " != " true" ]; then
156+ break
157+ fi
158+
159+ THREADS_CURSOR=$( echo " $THREADS_RESULT " | jq -r ' .data.repository.pullRequest.reviewThreads.pageInfo.endCursor' )
160+ if [ -z " $THREADS_CURSOR " ] || [ " $THREADS_CURSOR " = " null" ]; then
161+ echo " β Assertion failed: review thread pagination cursor missing while hasNextPage=true"
162+ exit 1
163+ fi
74164done
75165
76166# Filter regular comments from bot that aren't minimized, excluding:
77167# - "Didn't find any major issues" (no issues found)
78168# - "usage limits have been reached" (rate limit error, not a real review)
79- REGULAR_COMMENTS=$( echo " $RESULT " | jq " [.data.repository.pullRequest.comments.nodes [] | select(.author.login == \" ${BOT_LOGIN_GRAPHQL} \" and .isMinimized == false and (.body | test(\" Didn't find any major issues|usage limits have been reached\" ) | not))]" )
169+ REGULAR_COMMENTS=$( echo " $ALL_COMMENTS " | jq " [.[] | select(.author.login == \" ${BOT_LOGIN_GRAPHQL} \" and .isMinimized == false and (.body | test(\" Didn't find any major issues|usage limits have been reached\" ) | not))]" )
80170REGULAR_COUNT=$( echo " $REGULAR_COMMENTS " | jq ' length' )
81171
82172# Filter unresolved review threads from bot
83- UNRESOLVED_THREADS=$( echo " $RESULT " | jq " [.data.repository.pullRequest.reviewThreads.nodes [] | select(.isResolved == false and .comments.nodes[0].author.login == \" ${BOT_LOGIN_GRAPHQL} \" )]" )
173+ UNRESOLVED_THREADS=$( echo " $ALL_THREADS " | jq " [.[] | select(.isResolved == false and .comments.nodes[0].author.login == \" ${BOT_LOGIN_GRAPHQL} \" )]" )
84174UNRESOLVED_COUNT=$( echo " $UNRESOLVED_THREADS " | jq ' length' )
85175
86176TOTAL_UNRESOLVED=$(( REGULAR_COUNT + UNRESOLVED_COUNT))
87177
88178echo " Found ${REGULAR_COUNT} unminimized regular comment(s) from bot"
89179echo " Found ${UNRESOLVED_COUNT} unresolved review thread(s) from bot"
90180
91- if [ $TOTAL_UNRESOLVED -gt 0 ]; then
181+ if [ " $TOTAL_UNRESOLVED " -gt 0 ]; then
92182 echo " "
93183 echo " β Found ${TOTAL_UNRESOLVED} unresolved comment(s) from Codex in PR #${PR_NUMBER} "
94184 echo " "
@@ -107,7 +197,7 @@ if [ $TOTAL_UNRESOLVED -gt 0 ]; then
107197 echo " "
108198 echo " Please address or resolve all Codex comments before merging."
109199 exit 1
110- else
111- echo " β
No unresolved Codex comments found"
112- exit 0
113200fi
201+
202+ echo " β
No unresolved Codex comments found"
203+ exit 0
0 commit comments