From 0f979fd6c900f6ddd76072f4892ccc7c3525fd60 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 14 Mar 2026 07:34:47 +0000 Subject: [PATCH] fix: stuck PRs priority + STATE.md in first commit + 405 bug in dev-poll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. PRIORITY 1.5 in dev-poll: scan ALL open PRs for REQUEST_CHANGES or CI failure before picking new backlog issues. Stuck PRs get fixed first to avoid complex rebases piling up. 2. STATE.md written in worktree before claude starts (included in first commit, not a separate push that dismisses stale approvals). 3. Removed HTTP 405 from merge success check in dev-poll.sh (was fixed in dev-agent.sh but not here — 2 occurrences). --- dev/dev-agent.sh | 49 +++++++++++++++++--------------------- dev/dev-poll.sh | 61 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index 02e46ab..8bda468 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -545,6 +545,10 @@ else cd "$WORKTREE" git checkout -B "$BRANCH" origin/master 2>/dev/null git submodule update --init --recursive 2>/dev/null || true + + # Write STATE.md entry — will be included in the first commit + write_state_entry "in-progress" + # Symlink lib node_modules from main repo (submodule init doesn't run npm install) for lib_dir in "$REPO_ROOT"/onchain/lib/*/; do lib_name=$(basename "$lib_dir") @@ -856,45 +860,32 @@ fi # ============================================================================= # STATE.MD APPEND # ============================================================================= -# After each merge, append one line to STATE.md describing what now exists. -# Format: - [YYYY-MM-DD] what is now true about harb (#PR) -# The planner will collapse this into a compact snapshot later. -append_state_log() { - local state_file="${REPO_ROOT}/STATE.md" +# Write STATE.md entry in the worktree BEFORE implementation starts. +# This ensures the STATE.md update is part of the first commit, not a separate +# push that would dismiss stale approvals. +# Format: - [YYYY-MM-DD] description (#ISSUE) +write_state_entry() { + local status_word="${1:-in-progress}" # "in-progress" or "done" + local target="${WORKTREE:-$REPO_ROOT}" + local state_file="${target}/STATE.md" local today today=$(date -u +%Y-%m-%d) - # Derive a "what now exists" description from the issue title. - # Strip prefixes (feat:/fix:/refactor:) to get the essence. local description description=$(echo "$ISSUE_TITLE" | sed 's/^feat:\s*//i;s/^fix:\s*//i;s/^refactor:\s*//i') - local line="- [${today}] ${description} (#${PR_NUMBER})" - - # Create file with header if it doesn't exist - if [ ! -f "$state_file" ]; then - echo "# STATE.md — What harb currently is and does" > "$state_file" - echo "" >> "$state_file" - fi - - echo "$line" >> "$state_file" - - # Append to STATE.md on the PR branch, push before merge - local worktree="${WORKTREE:-}" - local target="${worktree:-$REPO_ROOT}" - local state_file="${target}/STATE.md" + local line="- [${today}] [${status_word}] ${description} (#${ISSUE})" if [ ! -f "$state_file" ]; then printf '# STATE.md — What harb currently is and does\n\n' > "$state_file" fi echo "$line" >> "$state_file" + log "STATE.md: ${line}" +} - cd "$target" - git add STATE.md 2>/dev/null || true - git diff --cached --quiet && return 0 - git commit -m "state: ${description} (#${PR_NUMBER})" --no-verify 2>/dev/null || true - git push origin "${BRANCH}" 2>/dev/null || log "STATE.md push failed — will be missing from this merge" - log "STATE.md updated: ${line}" +# Alias for backward compat +append_state_log() { + write_state_entry "done" } # MERGE HELPER @@ -948,6 +939,10 @@ do_merge() { if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then log "PR #${PR_NUMBER} merged!" + # Update STATE.md on master (pull merged changes first) + (cd "$REPO_ROOT" && git checkout master 2>/dev/null && git pull --ff-only origin master 2>/dev/null) || true + append_state_log || log "WARNING: STATE.md update failed (non-fatal)" + curl -sf -X DELETE \ -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/branches/${BRANCH}" >/dev/null 2>&1 || true diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index fb20191..0c5950a 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -150,7 +150,7 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then "${API}/pulls/${HAS_PR}/merge" \ -d '{"Do":"merge","delete_branch_after_merge":true}') - if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] || [ "$MERGE_CODE" = "405" ]; then + if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] ; then log "PR #${HAS_PR} merged! Closing #${ISSUE_NUM}" curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ -H "Content-Type: application/json" \ @@ -186,6 +186,63 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then fi fi +# ============================================================================= +# PRIORITY 1.5: any open PR with REQUEST_CHANGES or CI failure (stuck PRs) +# ============================================================================= +log "checking for stuck PRs" +OPEN_PRS=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/pulls?state=open&limit=20") + +for i in $(seq 0 $(($(echo "$OPEN_PRS" | jq 'length') - 1))); do + PR_NUM=$(echo "$OPEN_PRS" | jq -r ".[$i].number") + PR_BRANCH=$(echo "$OPEN_PRS" | jq -r ".[$i].head.ref") + PR_SHA=$(echo "$OPEN_PRS" | jq -r ".[$i].head.sha") + + # Extract issue number from branch name (fix/issue-NNN) + STUCK_ISSUE=$(echo "$PR_BRANCH" | grep -oP '(?<=fix/issue-)\d+' || true) + [ -z "$STUCK_ISSUE" ] && continue + + CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') || true + 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 + HAS_APPROVE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/pulls/${PR_NUM}/reviews" | \ + jq -r '[.[] | select(.state == "APPROVED") | select(.stale == false)] | length') || true + + # Try merge if approved + CI green + if [ "$CI_STATE" = "success" ] && [ "${HAS_APPROVE:-0}" -gt 0 ]; then + log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) approved + CI green → merging" + MERGE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/pulls/${PR_NUM}/merge" \ + -d '{"Do":"merge","delete_branch_after_merge":true}') + if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ]; then + log "PR #${PR_NUM} merged! Closing #${STUCK_ISSUE}" + curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/issues/${STUCK_ISSUE}" -d '{"state":"closed"}' >/dev/null 2>&1 || true + openclaw system event --text "✅ PR #${PR_NUM} merged! Issue #${STUCK_ISSUE} done." --mode now 2>/dev/null || true + fi + continue + fi + + # Stuck: REQUEST_CHANGES or CI failure → spawn agent + if [ "$CI_STATE" = "success" ] && [ "${HAS_CHANGES:-0}" -gt 0 ]; then + log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) has REQUEST_CHANGES — fixing first" + nohup "${SCRIPT_DIR}/dev-agent.sh" "$STUCK_ISSUE" >> "$LOGFILE" 2>&1 & + log "started dev-agent PID $! for stuck PR #${PR_NUM}" + exit 0 + elif [ "$CI_STATE" = "failure" ] || [ "$CI_STATE" = "error" ]; then + log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) CI failed — fixing first" + nohup "${SCRIPT_DIR}/dev-agent.sh" "$STUCK_ISSUE" >> "$LOGFILE" 2>&1 & + log "started dev-agent PID $! for stuck PR #${PR_NUM}" + exit 0 + fi +done + # ============================================================================= # PRIORITY 2: find ready backlog issues (pull system) # ============================================================================= @@ -236,7 +293,7 @@ for i in $(seq 0 $((BACKLOG_COUNT - 1))); do -H "Content-Type: application/json" \ "${API}/pulls/${EXISTING_PR}/merge" \ -d '{"Do":"merge","delete_branch_after_merge":true}') - if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] || [ "$MERGE_CODE" = "405" ]; then + if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] ; then log "PR #${EXISTING_PR} merged! Closing #${ISSUE_NUM}" curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ -H "Content-Type: application/json" \