diff --git a/dev/phase-handler.sh b/dev/phase-handler.sh index 17b9331..926deb6 100644 --- a/dev/phase-handler.sh +++ b/dev/phase-handler.sh @@ -42,11 +42,11 @@ do_merge() { return 0 fi - # HTTP 405 "not enough approvals" — structural, not transient — escalate immediately - if [ "$merge_http_code" = "405" ] && echo "$merge_body" | grep -qi "not enough"; then - log "do_merge: PR #${pr_num} blocked — not enough approvals (HTTP 405): ${merge_body:0:200}" + # HTTP 405 — merge requirements not met (approvals, branch protection); structural, not transient + if [ "$merge_http_code" = "405" ]; then + log "do_merge: PR #${pr_num} blocked — merge requirements not met (HTTP 405): ${merge_body:0:200}" printf 'PHASE:needs_human\nReason: %s\n' \ - "PR #${pr_num} merge blocked — not enough approvals (HTTP 405): ${merge_body:0:200}" \ + "PR #${pr_num} merge blocked — merge requirements not met (HTTP 405): ${merge_body:0:200}" \ > "$PHASE_FILE" return 2 fi @@ -366,19 +366,22 @@ Instructions: [ -n "$VERDICT" ] && log "verdict from formal review: $VERDICT" fi - # Skip injection if review-poll.sh already injected (sentinel present) + # Skip injection if review-poll.sh already injected (sentinel present). + # Exception: APPROVE always falls through so do_merge() runs even when + # review-poll injected first — prevents Claude writing PHASE:done on a + # failed merge without the orchestrator detecting the error. REVIEW_SENTINEL="/tmp/review-injected-${PROJECT_NAME}-${PR_NUMBER}" - if [ -n "$VERDICT" ] && [ -f "$REVIEW_SENTINEL" ]; then + if [ -n "$VERDICT" ] && [ -f "$REVIEW_SENTINEL" ] && [ "$VERDICT" != "APPROVE" ]; then log "review already injected by review-poll (sentinel exists) — skipping" rm -f "$REVIEW_SENTINEL" REVIEW_FOUND=true break fi + rm -f "$REVIEW_SENTINEL" # consume sentinel before APPROVE handling below if [ "$VERDICT" = "APPROVE" ]; then REVIEW_FOUND=true - do_merge "$PR_NUMBER" - _merge_rc=$? + _merge_rc=0; do_merge "$PR_NUMBER" || _merge_rc=$? if [ "$_merge_rc" -eq 0 ]; then # Merge succeeded — close issue and signal done curl -sf -X PATCH \ @@ -499,11 +502,11 @@ Instructions: # Don't inject anything — supervisor-poll.sh (#81) injects human replies, gardener-poll.sh as backup # ── PHASE: done ───────────────────────────────────────────────────────────── - # The agent already merged the PR and closed the issue. Just clean up local state. + # PR merged and issue closed (by orchestrator or Claude). Just clean up local state. elif [ "$phase" = "PHASE:done" ]; then - status "phase done — agent merged PR #${PR_NUMBER:-?}, cleaning up" + status "phase done — PR #${PR_NUMBER:-?} merged, cleaning up" - # Notify Matrix (agent already closed the issue and removed labels via API) + # Notify Matrix (issue already closed and labels removed via API) notify_ctx \ "✅ PR #${PR_NUMBER:-?} merged! Issue #${ISSUE} done." \ "✅ PR #${PR_NUMBER:-?} merged! Issue #${ISSUE} done."