Skip to content

Commit 2974392

Browse files
committed
🤖 fix: make PR readiness wait loop fail fast
Add --once status contract to wait_pr_codex.sh and wait_pr_checks.sh, then refactor wait_pr_ready.sh to poll both gates in one loop and exit on first terminal failure. Update AGENTS.md to document the single-command readiness flow. --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.26`_ <!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=0.26 -->
1 parent 8ae6328 commit 2974392

4 files changed

Lines changed: 404 additions & 132 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ When a PR exists, you MUST remain in this loop until the PR is fully ready:
104104
1. Push your latest fixes.
105105
2. Run local validation (`make verify-vendor`, `make test`, `make build`).
106106
3. Request review with `@codex review`.
107-
4. Run `./scripts/wait_pr_codex.sh <pr_number>` and wait for Codex.
107+
4. Run `./scripts/wait_pr_ready.sh <pr_number>` (it polls Codex + required checks concurrently and fails fast).
108108
5. If Codex leaves comments, address them, resolve threads with `./scripts/resolve_pr_comment.sh <thread_id>`, push, and repeat.
109-
6. After explicit Codex approval, run `./scripts/wait_pr_checks.sh <pr_number>` and wait for required checks to pass.
109+
6. If checks/mergeability fail, fix issues locally, push, and repeat.
110110

111111
The only early-stop exception is when the reviewer is clearly misunderstanding the intended change and further churn would be counterproductive. In that case, leave a clarifying PR comment and pause for human direction.

scripts/wait_pr_checks.sh

Lines changed: 180 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Wait for PR checks to complete
5-
# Usage: ./scripts/wait_pr_checks.sh <pr_number>
4+
# Wait for PR checks to complete.
5+
# Usage: ./scripts/wait_pr_checks.sh <pr_number> [--once]
6+
#
7+
# Exits:
8+
# 0 - PR checks and mergeability gates passed
9+
# 1 - terminal failure (conflicts, failing checks, unresolved comments, etc.)
10+
# 10 - still waiting for checks/mergeability (only in --once mode)
611

7-
if [ $# -eq 0 ]; then
8-
echo "Usage: $0 <pr_number>"
12+
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
13+
echo "Usage: $0 <pr_number> [--once]"
914
exit 1
1015
fi
1116

1217
PR_NUMBER=$1
18+
MODE="wait"
19+
20+
if [ $# -eq 2 ]; then
21+
if [ "$2" = "--once" ]; then
22+
MODE="once"
23+
else
24+
echo "❌ Unknown argument: '$2'" >&2
25+
echo "Usage: $0 <pr_number> [--once]" >&2
26+
exit 1
27+
fi
28+
fi
29+
30+
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
31+
CHECK_REVIEWS_SCRIPT="$SCRIPT_DIR/check_pr_reviews.sh"
32+
CHECK_CODEX_COMMENTS_SCRIPT="$SCRIPT_DIR/check_codex_comments.sh"
33+
34+
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
35+
echo "❌ PR number must be numeric. Got: '$PR_NUMBER'" >&2
36+
exit 1
37+
fi
38+
39+
for helper in "$CHECK_REVIEWS_SCRIPT" "$CHECK_CODEX_COMMENTS_SCRIPT"; do
40+
if [ ! -x "$helper" ]; then
41+
echo "❌ assertion failed: missing executable helper script: $helper" >&2
42+
exit 1
43+
fi
44+
done
1345

1446
# Check for dirty working tree
1547
if ! git diff-index --quiet HEAD --; then
@@ -73,61 +105,112 @@ if [[ "$LOCAL_HASH" != "$REMOTE_HASH" ]]; then
73105
exit 1
74106
fi
75107

76-
echo "⏳ Waiting for PR #$PR_NUMBER checks to complete..."
77-
echo ""
108+
LAST_MERGE_STATE="UNKNOWN"
109+
110+
CHECK_PR_CHECKS_ONCE() {
111+
local status
112+
local pr_state
113+
local mergeable
114+
local merge_state
115+
local checks
78116

79-
while true; do
80117
# Get PR status
81-
STATUS=$(gh pr view "$PR_NUMBER" --json mergeable,mergeStateStatus,state 2>/dev/null || echo "error")
118+
status=$(gh pr view "$PR_NUMBER" --json mergeable,mergeStateStatus,state 2>/dev/null || echo "error")
82119

83-
if [ "$STATUS" = "error" ]; then
120+
if [ "$status" = "error" ]; then
84121
echo "❌ Failed to get PR status. Does PR #$PR_NUMBER exist?"
85-
exit 1
122+
return 1
86123
fi
87124

88-
PR_STATE=$(echo "$STATUS" | jq -r '.state')
125+
pr_state=$(echo "$status" | jq -r '.state')
89126

90-
# Check if PR is already merged
91-
if [ "$PR_STATE" = "MERGED" ]; then
92-
echo "✅ PR #$PR_NUMBER has been merged!"
93-
exit 0
94-
fi
127+
case "$pr_state" in
128+
MERGED)
129+
echo "✅ PR #$PR_NUMBER has been merged!"
130+
return 0
131+
;;
132+
CLOSED)
133+
echo "❌ PR #$PR_NUMBER is closed (not merged)!"
134+
return 1
135+
;;
136+
OPEN)
137+
;;
138+
*)
139+
echo "❌ assertion failed: unexpected PR state '$pr_state' for PR #$PR_NUMBER" >&2
140+
return 1
141+
;;
142+
esac
95143

96-
# Check if PR is closed without merging
97-
if [ "$PR_STATE" = "CLOSED" ]; then
98-
echo "❌ PR #$PR_NUMBER is closed (not merged)!"
99-
exit 1
100-
fi
144+
mergeable=$(echo "$status" | jq -r '.mergeable')
145+
merge_state=$(echo "$status" | jq -r '.mergeStateStatus')
146+
LAST_MERGE_STATE="$merge_state"
101147

102-
MERGEABLE=$(echo "$STATUS" | jq -r '.mergeable')
103-
MERGE_STATE=$(echo "$STATUS" | jq -r '.mergeStateStatus')
148+
case "$mergeable" in
149+
MERGEABLE | CONFLICTING | UNKNOWN)
150+
;;
151+
*)
152+
echo "❌ assertion failed: unexpected mergeable status '$mergeable' for PR #$PR_NUMBER" >&2
153+
return 1
154+
;;
155+
esac
156+
157+
case "$merge_state" in
158+
BEHIND | BLOCKED | CLEAN | DIRTY | DRAFT | HAS_HOOKS | UNKNOWN | UNSTABLE)
159+
;;
160+
*)
161+
echo "❌ assertion failed: unexpected merge state '$merge_state' for PR #$PR_NUMBER" >&2
162+
return 1
163+
;;
164+
esac
104165

105166
# Check for bad merge status
106-
if [ "$MERGEABLE" = "CONFLICTING" ]; then
167+
if [ "$mergeable" = "CONFLICTING" ]; then
107168
echo "❌ PR has merge conflicts!"
108-
exit 1
169+
return 1
109170
fi
110171

111-
if [ "$MERGE_STATE" = "DIRTY" ]; then
172+
if [ "$merge_state" = "DIRTY" ]; then
112173
echo "❌ PR has merge conflicts!"
113-
exit 1
174+
return 1
114175
fi
115176

116-
if [ "$MERGE_STATE" = "BEHIND" ]; then
177+
if [ "$merge_state" = "BEHIND" ]; then
117178
echo "❌ PR is behind base branch. Rebase needed."
118179
echo ""
119180
echo "Run:"
120181
echo " git fetch origin"
121182
echo " git rebase origin/main"
122183
echo " git push --force-with-lease"
123-
exit 1
184+
return 1
124185
fi
125186

126187
# Get check status
127-
CHECKS=$(gh pr checks "$PR_NUMBER" 2>&1 || echo "pending")
188+
checks=$(gh pr checks "$PR_NUMBER" 2>&1 || echo "pending")
189+
190+
local has_fail=0
191+
local has_pending=0
192+
local has_pass=0
193+
194+
if echo "$checks" | grep -q "fail"; then
195+
has_fail=1
196+
fi
197+
198+
if echo "$checks" | grep -q "pending"; then
199+
has_pending=1
200+
fi
201+
202+
if echo "$checks" | grep -q "pass"; then
203+
has_pass=1
204+
fi
205+
206+
if [ "$has_fail" -eq 0 ] && [ "$has_pending" -eq 0 ] && [ "$has_pass" -eq 0 ]; then
207+
echo "❌ assertion failed: unable to classify 'gh pr checks' output for PR #$PR_NUMBER" >&2
208+
echo "$checks" >&2
209+
return 1
210+
fi
128211

129212
# Check for failures
130-
if echo "$CHECKS" | grep -q "fail"; then
213+
if [ "$has_fail" -eq 1 ]; then
131214
echo "❌ Some checks failed:"
132215
echo ""
133216
gh pr checks "$PR_NUMBER"
@@ -140,44 +223,92 @@ while true; do
140223
echo " make verify-vendor"
141224
echo " make test"
142225
echo " make build"
143-
exit 1
226+
return 1
144227
fi
145228

146229
# Check for unresolved review comments in the hot loop
147-
if ! ./scripts/check_pr_reviews.sh "$PR_NUMBER" >/dev/null 2>&1; then
230+
if ! "$CHECK_REVIEWS_SCRIPT" "$PR_NUMBER" >/dev/null 2>&1; then
148231
echo ""
149232
echo "❌ Unresolved review comments found!"
150233
echo " 👉 Tip: run ./scripts/check_pr_reviews.sh $PR_NUMBER to list them."
151-
./scripts/check_pr_reviews.sh "$PR_NUMBER"
152-
exit 1
234+
"$CHECK_REVIEWS_SCRIPT" "$PR_NUMBER"
235+
return 1
153236
fi
154237

155238
# Check if all checks passed and merge state is clean
156-
if echo "$CHECKS" | grep -q "pass" && ! echo "$CHECKS" | grep -qE "pending|fail"; then
157-
if [ "$MERGE_STATE" = "CLEAN" ]; then
239+
if [ "$has_pass" -eq 1 ] && [ "$has_pending" -eq 0 ] && [ "$has_fail" -eq 0 ]; then
240+
if [ "$merge_state" = "CLEAN" ]; then
158241
# Check for unresolved Codex comments
159242
echo "✅ All checks passed!"
160243
echo ""
161244
gh pr checks "$PR_NUMBER"
162245
echo ""
163246
echo "🤖 Checking for unresolved Codex comments..."
164-
if ./scripts/check_codex_comments.sh "$PR_NUMBER"; then
247+
if "$CHECK_CODEX_COMMENTS_SCRIPT" "$PR_NUMBER"; then
165248
echo ""
166249
echo "✅ PR is ready to merge!"
167-
exit 0
168-
else
169-
echo ""
170-
echo "❌ Please resolve Codex comments before merging."
171-
echo " 👉 Tip: use ./scripts/check_pr_reviews.sh $PR_NUMBER to list unresolved comments."
172-
exit 1
250+
return 0
173251
fi
174-
elif [ "$MERGE_STATE" = "BLOCKED" ]; then
175-
echo "⏳ All checks passed but still blocked (waiting for required checks)..."
252+
253+
echo ""
254+
echo "❌ Please resolve Codex comments before merging."
255+
echo " 👉 Tip: use ./scripts/check_pr_reviews.sh $PR_NUMBER to list unresolved comments."
256+
return 1
257+
fi
258+
259+
if [ "$merge_state" = "BLOCKED" ]; then
260+
return 10
176261
fi
262+
263+
echo "❌ assertion failed: checks passed but merge state '$merge_state' is not supported" >&2
264+
return 1
265+
fi
266+
267+
return 10
268+
}
269+
270+
if [ "$MODE" = "once" ]; then
271+
if CHECK_PR_CHECKS_ONCE; then
272+
rc=0
273+
else
274+
rc=$?
275+
fi
276+
277+
case "$rc" in
278+
0 | 1 | 10)
279+
exit "$rc"
280+
;;
281+
*)
282+
echo "❌ assertion failed: unexpected checks status code '$rc'" >&2
283+
exit 1
284+
;;
285+
esac
286+
fi
287+
288+
echo "⏳ Waiting for PR #$PR_NUMBER checks to complete..."
289+
echo ""
290+
291+
while true; do
292+
if CHECK_PR_CHECKS_ONCE; then
293+
rc=0
177294
else
178-
# Show current status
179-
echo -ne "\r⏳ Checks in progress... (${MERGE_STATE}) "
295+
rc=$?
180296
fi
181297

182-
sleep 5
298+
case "$rc" in
299+
0)
300+
exit 0
301+
;;
302+
1)
303+
exit 1
304+
;;
305+
10)
306+
echo -ne "\r⏳ Checks in progress... (${LAST_MERGE_STATE}) "
307+
sleep 5
308+
;;
309+
*)
310+
echo "❌ assertion failed: unexpected checks status code '$rc'" >&2
311+
exit 1
312+
;;
313+
esac
183314
done

0 commit comments

Comments
 (0)