From 1ab700c87af1ba7f1e4f47453e725830371dda62 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 19 Mar 2026 13:48:00 +0000 Subject: [PATCH] fix: feat: review + dev-poll skip CI gate for non-code PRs (#266) Add diff_has_code_files() and ci_required_for_pr() helpers to ci-helpers.sh. Non-code PRs (docs/*, formulas/*, evidence/*, *.md) that have no CI results now skip the CI gate instead of being stuck forever. Applied to: - review-pr.sh: CI gate skipped for non-code PRs - review-poll.sh: CI gate skipped for non-code PRs - dev-poll.sh: CI state treated as "success" for non-code PRs in orphan, stuck-PR, and backlog merge paths Co-Authored-By: Claude Opus 4.6 (1M context) --- dev/dev-poll.sh | 20 ++++++++++++++++++++ lib/ci-helpers.sh | 28 ++++++++++++++++++++++++++++ review/review-poll.sh | 11 +++++++---- review/review-pr.sh | 7 +++++-- 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index 90564ff..a5476ee 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -269,6 +269,12 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') || true + # Non-code PRs (docs, formulas, evidence) may have no CI — treat as passed + if ! ci_passed "$CI_STATE" && ! ci_required_for_pr "$HAS_PR"; then + CI_STATE="success" + log "PR #${HAS_PR} has no code files — treating CI as passed" + fi + # Check formal reviews HAS_APPROVE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/pulls/${HAS_PR}/reviews" | \ @@ -342,6 +348,13 @@ for i in $(seq 0 $(($(echo "$OPEN_PRS" | jq 'length') - 1))); do CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') || true + + # Non-code PRs (docs, formulas, evidence) may have no CI — treat as passed + if ! ci_passed "$CI_STATE" && ! ci_required_for_pr "$PR_NUM"; then + CI_STATE="success" + log "PR #${PR_NUM} has no code files — treating CI as passed" + fi + HAS_CHANGES=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/pulls/${PR_NUM}/reviews" | \ jq -r '[.[] | select(.state == "REQUEST_CHANGES") | select(.stale == false)] | length') || true @@ -423,6 +436,13 @@ for i in $(seq 0 $((BACKLOG_COUNT - 1))); do "${API}/pulls/${EXISTING_PR}" | jq -r '.head.sha') || true CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') || true + + # Non-code PRs (docs, formulas, evidence) may have no CI — treat as passed + if ! ci_passed "$CI_STATE" && ! ci_required_for_pr "$EXISTING_PR"; then + CI_STATE="success" + log "PR #${EXISTING_PR} has no code files — treating CI as passed" + fi + HAS_APPROVE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/pulls/${EXISTING_PR}/reviews" | \ jq -r '[.[] | select(.state == "APPROVED") | select(.stale == false)] | length') || true diff --git a/lib/ci-helpers.sh b/lib/ci-helpers.sh index a31d673..41dceb4 100644 --- a/lib/ci-helpers.sh +++ b/lib/ci-helpers.sh @@ -5,6 +5,34 @@ # ci_passed() requires: WOODPECKER_REPO_ID (from env.sh / project config) # classify_pipeline_failure() requires: woodpecker_api() (defined in env.sh) +# diff_has_code_files — check if file list (stdin, one per line) contains code files +# Non-code paths: docs/*, formulas/*, evidence/*, *.md +# Returns 0 if any code file found, 1 if all files are non-code. +diff_has_code_files() { + while IFS= read -r f; do + [ -z "$f" ] && continue + case "$f" in + docs/*|formulas/*|evidence/*) continue ;; + *.md) continue ;; + *) return 0 ;; + esac + done + return 1 +} + +# ci_required_for_pr — check if CI is needed for this PR +# Returns 0 if PR has code files (CI required), 1 if non-code only (CI not required). +ci_required_for_pr() { + local pr_num="$1" + local files + files=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${CODEBERG_API}/pulls/${pr_num}/files" | jq -r '.[].filename' 2>/dev/null) || return 0 + if [ -z "$files" ]; then + return 0 # empty file list — require CI as safety default + fi + echo "$files" | diff_has_code_files +} + # ci_passed — check if CI is passing (or no CI configured) # Returns 0 if state is "success", or if no CI is configured and # state is empty/pending/unknown. diff --git a/review/review-poll.sh b/review/review-poll.sh index 39bd450..20dda63 100755 --- a/review/review-poll.sh +++ b/review/review-poll.sh @@ -186,11 +186,14 @@ while IFS= read -r line; do CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API_BASE}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') - # Skip if CI is running/failed. Allow "success" or no CI configured (empty/pending with no pipelines) + # Skip if CI is running/failed. Allow "success", no CI configured, or non-code PRs if ! ci_passed "$CI_STATE"; then - log " #${PR_NUM} CI=${CI_STATE}, skip" - SKIPPED=$((SKIPPED + 1)) - continue + if ci_required_for_pr "$PR_NUM"; then + log " #${PR_NUM} CI=${CI_STATE}, skip" + SKIPPED=$((SKIPPED + 1)) + continue + fi + log " #${PR_NUM} CI=${CI_STATE} but no code files — proceeding" fi # Check formal Codeberg reviews (not comment markers) diff --git a/review/review-pr.sh b/review/review-pr.sh index b5ed2c3..ce5b200 100755 --- a/review/review-pr.sh +++ b/review/review-pr.sh @@ -181,8 +181,11 @@ CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API_BASE}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') if ! ci_passed "$CI_STATE"; then - log "SKIP: CI=${CI_STATE}" - exit 0 + if ci_required_for_pr "$PR_NUMBER"; then + log "SKIP: CI=${CI_STATE}" + exit 0 + fi + log "CI=${CI_STATE} but PR has no code files — skipping CI gate" fi # --- Check for existing reviews ---