From 729c5196103396c1f0d1231bbbf8164fcc306e75 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 9 Feb 2026 13:17:11 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A4=96=20fix:=20make=20PR=20readiness?= =?UTF-8?q?=20wait=20loop=20fail=20fast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`_ --- AGENTS.md | 4 +- scripts/wait_pr_checks.sh | 229 ++++++++++++++++++++++++++++++-------- scripts/wait_pr_codex.sh | 182 ++++++++++++++++++++++-------- scripts/wait_pr_ready.sh | 121 ++++++++++++++------ 4 files changed, 404 insertions(+), 132 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d1c94d0e..b1de6b57 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,8 +104,8 @@ When a PR exists, you MUST remain in this loop until the PR is fully ready: 1. Push your latest fixes. 2. Run local validation (`make verify-vendor`, `make test`, `make build`). 3. Request review with `@codex review`. -4. Run `./scripts/wait_pr_codex.sh ` and wait for Codex. +4. Run `./scripts/wait_pr_ready.sh ` (it polls Codex + required checks concurrently and fails fast). 5. If Codex leaves comments, address them, resolve threads with `./scripts/resolve_pr_comment.sh `, push, and repeat. -6. After explicit Codex approval, run `./scripts/wait_pr_checks.sh ` and wait for required checks to pass. +6. If checks/mergeability fail, fix issues locally, push, and repeat. 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. diff --git a/scripts/wait_pr_checks.sh b/scripts/wait_pr_checks.sh index 3c6e2d14..e02c85ee 100755 --- a/scripts/wait_pr_checks.sh +++ b/scripts/wait_pr_checks.sh @@ -1,15 +1,47 @@ #!/usr/bin/env bash set -euo pipefail -# Wait for PR checks to complete -# Usage: ./scripts/wait_pr_checks.sh +# Wait for PR checks to complete. +# Usage: ./scripts/wait_pr_checks.sh [--once] +# +# Exits: +# 0 - PR checks and mergeability gates passed +# 1 - terminal failure (conflicts, failing checks, unresolved comments, etc.) +# 10 - still waiting for checks/mergeability (only in --once mode) -if [ $# -eq 0 ]; then - echo "Usage: $0 " +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + echo "Usage: $0 [--once]" exit 1 fi PR_NUMBER=$1 +MODE="wait" + +if [ $# -eq 2 ]; then + if [ "$2" = "--once" ]; then + MODE="once" + else + echo "❌ Unknown argument: '$2'" >&2 + echo "Usage: $0 [--once]" >&2 + exit 1 + fi +fi + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +CHECK_REVIEWS_SCRIPT="$SCRIPT_DIR/check_pr_reviews.sh" +CHECK_CODEX_COMMENTS_SCRIPT="$SCRIPT_DIR/check_codex_comments.sh" + +if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then + echo "❌ PR number must be numeric. Got: '$PR_NUMBER'" >&2 + exit 1 +fi + +for helper in "$CHECK_REVIEWS_SCRIPT" "$CHECK_CODEX_COMMENTS_SCRIPT"; do + if [ ! -x "$helper" ]; then + echo "❌ assertion failed: missing executable helper script: $helper" >&2 + exit 1 + fi +done # Check for dirty working tree if ! git diff-index --quiet HEAD --; then @@ -73,61 +105,112 @@ if [[ "$LOCAL_HASH" != "$REMOTE_HASH" ]]; then exit 1 fi -echo "⏳ Waiting for PR #$PR_NUMBER checks to complete..." -echo "" +LAST_MERGE_STATE="UNKNOWN" + +CHECK_PR_CHECKS_ONCE() { + local status + local pr_state + local mergeable + local merge_state + local checks -while true; do # Get PR status - STATUS=$(gh pr view "$PR_NUMBER" --json mergeable,mergeStateStatus,state 2>/dev/null || echo "error") + status=$(gh pr view "$PR_NUMBER" --json mergeable,mergeStateStatus,state 2>/dev/null || echo "error") - if [ "$STATUS" = "error" ]; then + if [ "$status" = "error" ]; then echo "❌ Failed to get PR status. Does PR #$PR_NUMBER exist?" - exit 1 + return 1 fi - PR_STATE=$(echo "$STATUS" | jq -r '.state') + pr_state=$(echo "$status" | jq -r '.state') - # Check if PR is already merged - if [ "$PR_STATE" = "MERGED" ]; then - echo "✅ PR #$PR_NUMBER has been merged!" - exit 0 - fi + case "$pr_state" in + MERGED) + echo "✅ PR #$PR_NUMBER has been merged!" + return 0 + ;; + CLOSED) + echo "❌ PR #$PR_NUMBER is closed (not merged)!" + return 1 + ;; + OPEN) + ;; + *) + echo "❌ assertion failed: unexpected PR state '$pr_state' for PR #$PR_NUMBER" >&2 + return 1 + ;; + esac - # Check if PR is closed without merging - if [ "$PR_STATE" = "CLOSED" ]; then - echo "❌ PR #$PR_NUMBER is closed (not merged)!" - exit 1 - fi + mergeable=$(echo "$status" | jq -r '.mergeable') + merge_state=$(echo "$status" | jq -r '.mergeStateStatus') + LAST_MERGE_STATE="$merge_state" - MERGEABLE=$(echo "$STATUS" | jq -r '.mergeable') - MERGE_STATE=$(echo "$STATUS" | jq -r '.mergeStateStatus') + case "$mergeable" in + MERGEABLE | CONFLICTING | UNKNOWN) + ;; + *) + echo "❌ assertion failed: unexpected mergeable status '$mergeable' for PR #$PR_NUMBER" >&2 + return 1 + ;; + esac + + case "$merge_state" in + BEHIND | BLOCKED | CLEAN | DIRTY | DRAFT | HAS_HOOKS | UNKNOWN | UNSTABLE) + ;; + *) + echo "❌ assertion failed: unexpected merge state '$merge_state' for PR #$PR_NUMBER" >&2 + return 1 + ;; + esac # Check for bad merge status - if [ "$MERGEABLE" = "CONFLICTING" ]; then + if [ "$mergeable" = "CONFLICTING" ]; then echo "❌ PR has merge conflicts!" - exit 1 + return 1 fi - if [ "$MERGE_STATE" = "DIRTY" ]; then + if [ "$merge_state" = "DIRTY" ]; then echo "❌ PR has merge conflicts!" - exit 1 + return 1 fi - if [ "$MERGE_STATE" = "BEHIND" ]; then + if [ "$merge_state" = "BEHIND" ]; then echo "❌ PR is behind base branch. Rebase needed." echo "" echo "Run:" echo " git fetch origin" echo " git rebase origin/main" echo " git push --force-with-lease" - exit 1 + return 1 fi # Get check status - CHECKS=$(gh pr checks "$PR_NUMBER" 2>&1 || echo "pending") + checks=$(gh pr checks "$PR_NUMBER" 2>&1 || echo "pending") + + local has_fail=0 + local has_pending=0 + local has_pass=0 + + if echo "$checks" | grep -q "fail"; then + has_fail=1 + fi + + if echo "$checks" | grep -q "pending"; then + has_pending=1 + fi + + if echo "$checks" | grep -q "pass"; then + has_pass=1 + fi + + if [ "$has_fail" -eq 0 ] && [ "$has_pending" -eq 0 ] && [ "$has_pass" -eq 0 ]; then + echo "❌ assertion failed: unable to classify 'gh pr checks' output for PR #$PR_NUMBER" >&2 + echo "$checks" >&2 + return 1 + fi # Check for failures - if echo "$CHECKS" | grep -q "fail"; then + if [ "$has_fail" -eq 1 ]; then echo "❌ Some checks failed:" echo "" gh pr checks "$PR_NUMBER" @@ -140,44 +223,92 @@ while true; do echo " make verify-vendor" echo " make test" echo " make build" - exit 1 + return 1 fi # Check for unresolved review comments in the hot loop - if ! ./scripts/check_pr_reviews.sh "$PR_NUMBER" >/dev/null 2>&1; then + if ! "$CHECK_REVIEWS_SCRIPT" "$PR_NUMBER" >/dev/null 2>&1; then echo "" echo "❌ Unresolved review comments found!" echo " 👉 Tip: run ./scripts/check_pr_reviews.sh $PR_NUMBER to list them." - ./scripts/check_pr_reviews.sh "$PR_NUMBER" - exit 1 + "$CHECK_REVIEWS_SCRIPT" "$PR_NUMBER" + return 1 fi # Check if all checks passed and merge state is clean - if echo "$CHECKS" | grep -q "pass" && ! echo "$CHECKS" | grep -qE "pending|fail"; then - if [ "$MERGE_STATE" = "CLEAN" ]; then + if [ "$has_pass" -eq 1 ] && [ "$has_pending" -eq 0 ] && [ "$has_fail" -eq 0 ]; then + if [ "$merge_state" = "CLEAN" ]; then # Check for unresolved Codex comments echo "✅ All checks passed!" echo "" gh pr checks "$PR_NUMBER" echo "" echo "🤖 Checking for unresolved Codex comments..." - if ./scripts/check_codex_comments.sh "$PR_NUMBER"; then + if "$CHECK_CODEX_COMMENTS_SCRIPT" "$PR_NUMBER"; then echo "" echo "✅ PR is ready to merge!" - exit 0 - else - echo "" - echo "❌ Please resolve Codex comments before merging." - echo " 👉 Tip: use ./scripts/check_pr_reviews.sh $PR_NUMBER to list unresolved comments." - exit 1 + return 0 fi - elif [ "$MERGE_STATE" = "BLOCKED" ]; then - echo "⏳ All checks passed but still blocked (waiting for required checks)..." + + echo "" + echo "❌ Please resolve Codex comments before merging." + echo " 👉 Tip: use ./scripts/check_pr_reviews.sh $PR_NUMBER to list unresolved comments." + return 1 + fi + + if [ "$merge_state" = "BLOCKED" ]; then + return 10 fi + + echo "❌ assertion failed: checks passed but merge state '$merge_state' is not supported" >&2 + return 1 + fi + + return 10 +} + +if [ "$MODE" = "once" ]; then + if CHECK_PR_CHECKS_ONCE; then + rc=0 + else + rc=$? + fi + + case "$rc" in + 0 | 1 | 10) + exit "$rc" + ;; + *) + echo "❌ assertion failed: unexpected checks status code '$rc'" >&2 + exit 1 + ;; + esac +fi + +echo "⏳ Waiting for PR #$PR_NUMBER checks to complete..." +echo "" + +while true; do + if CHECK_PR_CHECKS_ONCE; then + rc=0 else - # Show current status - echo -ne "\r⏳ Checks in progress... (${MERGE_STATE}) " + rc=$? fi - sleep 5 + case "$rc" in + 0) + exit 0 + ;; + 1) + exit 1 + ;; + 10) + echo -ne "\r⏳ Checks in progress... (${LAST_MERGE_STATE}) " + sleep 5 + ;; + *) + echo "❌ assertion failed: unexpected checks status code '$rc'" >&2 + exit 1 + ;; + esac done diff --git a/scripts/wait_pr_codex.sh b/scripts/wait_pr_codex.sh index 3e4044c9..b7d83032 100755 --- a/scripts/wait_pr_codex.sh +++ b/scripts/wait_pr_codex.sh @@ -3,25 +3,45 @@ set -euo pipefail # Wait for Codex to respond to a `@codex review` request. # -# Usage: ./scripts/wait_pr_codex.sh +# Usage: ./scripts/wait_pr_codex.sh [--once] # # Exits: # 0 - Codex approved (posts an explicit approval comment) # 1 - Codex left comments to address OR failed to review (e.g. rate limit) +# 10 - still waiting for Codex response (only in --once mode) -if [ $# -eq 0 ]; then - echo "Usage: $0 " +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + echo "Usage: $0 [--once]" exit 1 fi PR_NUMBER=$1 +MODE="wait" + +if [ $# -eq 2 ]; then + if [ "$2" = "--once" ]; then + MODE="once" + else + echo "❌ Unknown argument: '$2'" >&2 + echo "Usage: $0 [--once]" >&2 + exit 1 + fi +fi + BOT_LOGIN_GRAPHQL="chatgpt-codex-connector" +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +CHECK_CODEX_COMMENTS_SCRIPT="$SCRIPT_DIR/check_codex_comments.sh" if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then echo "❌ PR number must be numeric. Got: '$PR_NUMBER'" exit 1 fi +if [ ! -x "$CHECK_CODEX_COMMENTS_SCRIPT" ]; then + echo "❌ assertion failed: missing executable helper script: $CHECK_CODEX_COMMENTS_SCRIPT" >&2 + exit 1 +fi + # Keep these regexes in sync with ./scripts/check_codex_comments.sh. CODEX_APPROVAL_REGEX="Didn't find any major issues" CODEX_RATE_LIMIT_REGEX="usage limits have been reached" @@ -262,39 +282,53 @@ FETCH_ALL_THREADS() { echo "$all_threads" } -echo "⏳ Waiting for Codex review on PR #$PR_NUMBER..." -echo "" -echo "Tip: after you comment '@codex review', Codex will respond with either:" -echo " - review comments / threads to address (script exits 1)" -echo " - an explicit approval comment (script exits 0)" -echo "" - -while true; do - PR_STATE_RESULT=$(FETCH_PR_STATE) - PR_STATE=$(echo "$PR_STATE_RESULT" | jq -r '.data.repository.pullRequest.state // empty') - - if [[ -z "$PR_STATE" ]]; then +LAST_REQUEST_AT="" + +CHECK_CODEX_STATUS_ONCE() { + local pr_state_result + local pr_state + local all_comments + local all_threads + local request_at + local rate_limit_comment + local approval_comment + local codex_response_count_comments + local codex_response_count_threads + local codex_response_count + local check_output + + pr_state_result=$(FETCH_PR_STATE) + pr_state=$(echo "$pr_state_result" | jq -r '.data.repository.pullRequest.state // empty') + + if [[ -z "$pr_state" ]]; then echo "❌ Unable to fetch PR state for #$PR_NUMBER in ${OWNER}/${REPO}." >&2 - exit 1 + return 1 fi - if [ "$PR_STATE" = "MERGED" ]; then - echo "✅ PR #$PR_NUMBER has been merged!" - exit 0 - fi - - if [ "$PR_STATE" = "CLOSED" ]; then - echo "❌ PR #$PR_NUMBER is closed (not merged)!" - exit 1 - fi + case "$pr_state" in + MERGED) + echo "✅ PR #$PR_NUMBER has been merged!" + return 0 + ;; + CLOSED) + echo "❌ PR #$PR_NUMBER is closed (not merged)!" + return 1 + ;; + OPEN) + ;; + *) + echo "❌ assertion failed: unexpected PR state '$pr_state' for PR #$PR_NUMBER" >&2 + return 1 + ;; + esac - ALL_COMMENTS=$(FETCH_ALL_COMMENTS) - ALL_THREADS=$(FETCH_ALL_THREADS) + all_comments=$(FETCH_ALL_COMMENTS) + all_threads=$(FETCH_ALL_THREADS) # Ignore Codex's own comments since they mention "@codex review" in boilerplate. - REQUEST_AT=$(echo "$ALL_COMMENTS" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" '[.[] | select(.author.login != $bot and (.body | contains("@codex review")))] | sort_by(.createdAt) | last | .createdAt // empty') + request_at=$(echo "$all_comments" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" '[.[] | select(.author.login != $bot and (.body | contains("@codex review")))] | sort_by(.createdAt) | last | .createdAt // empty') - if [[ -z "$REQUEST_AT" ]]; then + if [[ -z "$request_at" ]]; then echo "❌ No '@codex review' comment found on PR #$PR_NUMBER." >&2 echo "" >&2 echo "Post one (example):" >&2 @@ -303,50 +337,100 @@ while true; do echo " " >&2 echo " Please take another look." >&2 echo " EOF" >&2 - exit 1 + return 1 fi + LAST_REQUEST_AT="$request_at" + # If Codex can't run (usage limits, etc) it posts a comment we shouldn't treat as "approval". - RATE_LIMIT_COMMENT=$(echo "$ALL_COMMENTS" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$REQUEST_AT" --arg regex "$CODEX_RATE_LIMIT_REGEX" '[.[] | select(.author.login == $bot and .createdAt > $request_at and (.body | test($regex))) | {createdAt, body}] | sort_by(.createdAt) | last // empty | .body // empty') + rate_limit_comment=$(echo "$all_comments" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$request_at" --arg regex "$CODEX_RATE_LIMIT_REGEX" '[.[] | select(.author.login == $bot and .createdAt > $request_at and (.body | test($regex))) | {createdAt, body}] | sort_by(.createdAt) | last // empty | .body // empty') - if [[ -n "$RATE_LIMIT_COMMENT" ]]; then + if [[ -n "$rate_limit_comment" ]]; then echo "" echo "❌ Codex was unable to review (usage limits)." echo "" - echo "$RATE_LIMIT_COMMENT" - exit 1 + echo "$rate_limit_comment" + return 1 fi - APPROVAL_COMMENT=$(echo "$ALL_COMMENTS" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$REQUEST_AT" --arg regex "$CODEX_APPROVAL_REGEX" '[.[] | select(.author.login == $bot and .createdAt > $request_at and (.body | test($regex))) | {createdAt, body}] | sort_by(.createdAt) | last // empty | .body // empty') + approval_comment=$(echo "$all_comments" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$request_at" --arg regex "$CODEX_APPROVAL_REGEX" '[.[] | select(.author.login == $bot and .createdAt > $request_at and (.body | test($regex))) | {createdAt, body}] | sort_by(.createdAt) | last // empty | .body // empty') - if [[ -n "$APPROVAL_COMMENT" ]]; then + if [[ -n "$approval_comment" ]]; then echo "" echo "✅ Codex approved PR #$PR_NUMBER" echo "" - echo "$APPROVAL_COMMENT" - exit 0 + echo "$approval_comment" + return 0 fi - CODEX_RESPONSE_COUNT_COMMENTS=$(echo "$ALL_COMMENTS" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$REQUEST_AT" '[.[] | select(.author.login == $bot and .createdAt > $request_at)] | length') - CODEX_RESPONSE_COUNT_THREADS=$(echo "$ALL_THREADS" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$REQUEST_AT" '[.[] | select((.comments.nodes | length) > 0 and .comments.nodes[0].author.login == $bot and .comments.nodes[0].createdAt > $request_at)] | length') - CODEX_RESPONSE_COUNT=$((CODEX_RESPONSE_COUNT_COMMENTS + CODEX_RESPONSE_COUNT_THREADS)) + codex_response_count_comments=$(echo "$all_comments" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$request_at" '[.[] | select(.author.login == $bot and .createdAt > $request_at)] | length') + codex_response_count_threads=$(echo "$all_threads" | jq -r --arg bot "$BOT_LOGIN_GRAPHQL" --arg request_at "$request_at" '[.[] | select((.comments.nodes | length) > 0 and .comments.nodes[0].author.login == $bot and .comments.nodes[0].createdAt > $request_at)] | length') + codex_response_count=$((codex_response_count_comments + codex_response_count_threads)) - if [ "$CODEX_RESPONSE_COUNT" -eq 0 ]; then - echo -ne "\r⏳ Waiting for Codex response... (requested at ${REQUEST_AT}) " - sleep 5 - continue + if [ "$codex_response_count" -eq 0 ]; then + return 10 fi # Codex responded to the latest @codex review request; defer to check_codex_comments.sh for # unresolved comment/thread detection so we don't duplicate filtering logic here. - if ! CHECK_OUTPUT=$(./scripts/check_codex_comments.sh "$PR_NUMBER" 2>&1); then + if ! check_output=$("$CHECK_CODEX_COMMENTS_SCRIPT" "$PR_NUMBER" 2>&1); then echo "" - echo "$CHECK_OUTPUT" - exit 1 + echo "$check_output" + return 1 fi echo "" echo "❌ Codex responded, but no explicit approval comment was found after the latest '@codex review'." echo " 👉 If you expected approval, re-comment '@codex review' and run this script again." - exit 1 + return 1 +} + +if [ "$MODE" = "once" ]; then + if CHECK_CODEX_STATUS_ONCE; then + rc=0 + else + rc=$? + fi + + case "$rc" in + 0 | 1 | 10) + exit "$rc" + ;; + *) + echo "❌ assertion failed: unexpected Codex status code '$rc'" >&2 + exit 1 + ;; + esac +fi + +echo "⏳ Waiting for Codex review on PR #$PR_NUMBER..." +echo "" +echo "Tip: after you comment '@codex review', Codex will respond with either:" +echo " - review comments / threads to address (script exits 1)" +echo " - an explicit approval comment (script exits 0)" +echo "" + +while true; do + if CHECK_CODEX_STATUS_ONCE; then + rc=0 + else + rc=$? + fi + + case "$rc" in + 0) + exit 0 + ;; + 1) + exit 1 + ;; + 10) + echo -ne "\r⏳ Waiting for Codex response... (requested at ${LAST_REQUEST_AT}) " + sleep 5 + ;; + *) + echo "❌ assertion failed: unexpected Codex status code '$rc'" >&2 + exit 1 + ;; + esac done diff --git a/scripts/wait_pr_ready.sh b/scripts/wait_pr_ready.sh index acc53d87..b1c707f3 100755 --- a/scripts/wait_pr_ready.sh +++ b/scripts/wait_pr_ready.sh @@ -4,12 +4,12 @@ set -euo pipefail # Wait for a PR to become merge-ready by enforcing the Codex + CI loop. # Usage: ./scripts/wait_pr_ready.sh # -# This script orchestrates: -# 1) wait_pr_codex.sh - waits for an explicit Codex response/approval -# 2) wait_pr_checks.sh - waits for required CI checks and mergeability +# This script orchestrates Codex + checks in one polling loop: +# 1) wait_pr_codex.sh --once +# 2) wait_pr_checks.sh --once # -# It cannot auto-fix feedback; if either phase fails, address feedback, push, -# re-request review (`@codex review`), then run this script again. +# It exits immediately on the first terminal failure and succeeds only when +# both gates report success. if [ $# -ne 1 ]; then echo "Usage: $0 " >&2 @@ -40,34 +40,91 @@ for required_cmd in gh jq git; do fi done -echo "🚦 Waiting for PR #$PR_NUMBER to become ready (Codex + CI)..." -echo "" +status_from_rc() { + local rc="$1" -echo "Step 1/2: Waiting for Codex review on latest @codex review request..." -if ! "$WAIT_CODEX_SCRIPT" "$PR_NUMBER"; then - echo "" - echo "❌ Codex phase did not pass." - echo " Address feedback (or retry if Codex was rate-limited), push, and request review again:" - echo "" - echo " gh pr comment $PR_NUMBER --body-file - <<'EOF'" - echo " @codex review" - echo "" - echo " Please take another look." - echo " EOF" - echo "" - exit 1 -fi + case "$rc" in + 0) + echo "passed" + ;; + 10) + echo "pending" + ;; + 1) + echo "failed" + ;; + *) + echo "❌ assertion failed: unexpected phase status code '$rc'" >&2 + return 1 + ;; + esac +} +echo "🚦 Waiting for PR #$PR_NUMBER to become ready (Codex + CI, fail-fast)..." echo "" -echo "✅ Codex approved the latest review request." -echo "" -echo "Step 2/2: Waiting for required checks and mergeability..." -if ! "$WAIT_CHECKS_SCRIPT" "$PR_NUMBER"; then - echo "" - echo "❌ CI/mergeability phase did not pass." - echo " Fix issues locally, push, and rerun this script." - exit 1 -fi -echo "" -echo "🎉 PR #$PR_NUMBER is ready: Codex approved and required checks passed." +while true; do + if CODEX_OUT=$("$WAIT_CODEX_SCRIPT" "$PR_NUMBER" --once 2>&1); then + CODEX_RC=0 + else + CODEX_RC=$? + fi + + if CHECKS_OUT=$("$WAIT_CHECKS_SCRIPT" "$PR_NUMBER" --once 2>&1); then + CHECKS_RC=0 + else + CHECKS_RC=$? + fi + + CODEX_STATUS=$(status_from_rc "$CODEX_RC") || exit 1 + CHECKS_STATUS=$(status_from_rc "$CHECKS_RC") || exit 1 + + echo -ne "\r⏳ Gate status: Codex=${CODEX_STATUS} | Checks=${CHECKS_STATUS} " + + if [ "$CODEX_RC" -eq 1 ] || [ "$CHECKS_RC" -eq 1 ]; then + echo "" + echo "" + echo "❌ PR #$PR_NUMBER is not ready." + + if [ "$CODEX_RC" -eq 1 ]; then + echo "" + echo "--- Codex gate output ---" + if [ -n "$CODEX_OUT" ]; then + echo "$CODEX_OUT" + else + echo "(no output)" + fi + echo "" + echo "Address Codex feedback (or retry if Codex was rate-limited), push, and request review again:" + echo "" + echo " gh pr comment $PR_NUMBER --body-file - <<'EOF'" + echo " @codex review" + echo "" + echo " Please take another look." + echo " EOF" + fi + + if [ "$CHECKS_RC" -eq 1 ]; then + echo "" + echo "--- Checks gate output ---" + if [ -n "$CHECKS_OUT" ]; then + echo "$CHECKS_OUT" + else + echo "(no output)" + fi + echo "" + echo "Fix issues locally, push, and rerun this script." + fi + + exit 1 + fi + + if [ "$CODEX_RC" -eq 0 ] && [ "$CHECKS_RC" -eq 0 ]; then + echo "" + echo "" + echo "🎉 PR #$PR_NUMBER is ready: Codex approved and required checks passed." + exit 0 + fi + + sleep 5 +done From 49996511fe482237f214b293099a4d27f1f20ef6 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 9 Feb 2026 13:36:00 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A4=96=20fix:=20short-circuit=20check?= =?UTF-8?q?s=20when=20Codex=20gate=20already=20failed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update wait_pr_ready.sh to exit immediately when wait_pr_codex.sh --once returns a terminal failure, instead of always invoking wait_pr_checks.sh first. This preserves true fail-fast behavior and avoids unnecessary delay when Codex has already produced actionable feedback. --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.26`_ --- scripts/wait_pr_ready.sh | 69 +++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/scripts/wait_pr_ready.sh b/scripts/wait_pr_ready.sh index b1c707f3..80bfa2c0 100755 --- a/scripts/wait_pr_ready.sh +++ b/scripts/wait_pr_ready.sh @@ -70,52 +70,55 @@ while true; do CODEX_RC=$? fi + CODEX_STATUS=$(status_from_rc "$CODEX_RC") || exit 1 + + # True fail-fast behavior: if Codex is already terminal-failed, exit immediately + # without waiting for the checks gate. + if [ "$CODEX_RC" -eq 1 ]; then + echo -ne "\r⏳ Gate status: Codex=${CODEX_STATUS} | Checks=skipped " + echo "" + echo "" + echo "❌ PR #$PR_NUMBER is not ready." + echo "" + echo "--- Codex gate output ---" + if [ -n "$CODEX_OUT" ]; then + echo "$CODEX_OUT" + else + echo "(no output)" + fi + echo "" + echo "Address Codex feedback (or retry if Codex was rate-limited), push, and request review again:" + echo "" + echo " gh pr comment $PR_NUMBER --body-file - <<'EOF'" + echo " @codex review" + echo "" + echo " Please take another look." + echo " EOF" + exit 1 + fi + if CHECKS_OUT=$("$WAIT_CHECKS_SCRIPT" "$PR_NUMBER" --once 2>&1); then CHECKS_RC=0 else CHECKS_RC=$? fi - CODEX_STATUS=$(status_from_rc "$CODEX_RC") || exit 1 CHECKS_STATUS=$(status_from_rc "$CHECKS_RC") || exit 1 - echo -ne "\r⏳ Gate status: Codex=${CODEX_STATUS} | Checks=${CHECKS_STATUS} " - if [ "$CODEX_RC" -eq 1 ] || [ "$CHECKS_RC" -eq 1 ]; then + if [ "$CHECKS_RC" -eq 1 ]; then echo "" echo "" echo "❌ PR #$PR_NUMBER is not ready." - - if [ "$CODEX_RC" -eq 1 ]; then - echo "" - echo "--- Codex gate output ---" - if [ -n "$CODEX_OUT" ]; then - echo "$CODEX_OUT" - else - echo "(no output)" - fi - echo "" - echo "Address Codex feedback (or retry if Codex was rate-limited), push, and request review again:" - echo "" - echo " gh pr comment $PR_NUMBER --body-file - <<'EOF'" - echo " @codex review" - echo "" - echo " Please take another look." - echo " EOF" - fi - - if [ "$CHECKS_RC" -eq 1 ]; then - echo "" - echo "--- Checks gate output ---" - if [ -n "$CHECKS_OUT" ]; then - echo "$CHECKS_OUT" - else - echo "(no output)" - fi - echo "" - echo "Fix issues locally, push, and rerun this script." + echo "" + echo "--- Checks gate output ---" + if [ -n "$CHECKS_OUT" ]; then + echo "$CHECKS_OUT" + else + echo "(no output)" fi - + echo "" + echo "Fix issues locally, push, and rerun this script." exit 1 fi