fix: stuck PRs priority + STATE.md in first commit + 405 bug in dev-poll

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).
This commit is contained in:
openhands 2026-03-14 07:34:47 +00:00
parent 793dafdb8a
commit 0f979fd6c9
2 changed files with 81 additions and 29 deletions

View file

@ -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] <in-progress|done> 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

View file

@ -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" \