fix: feat: dev-agent merges its own PRs via non-admin Codeberg account (#172)
- phase-handler.sh: remove do_merge(); on APPROVAL inject exact API commands for agent to merge+close directly; PHASE:done now only does local cleanup (tmux, worktree, labels) — merge already done - dev-agent.sh: update PHASE_PROTOCOL_INSTRUCTIONS — Approved means merge via API, close issue, then write PHASE:done - dev-poll.sh: remove try_merge_or_rebase(); for approved+CI-green orphaned PRs, spawn dev-agent (recovery mode) to merge instead - .env.example: document new token roles (CODEBERG_TOKEN = bot for push/PR/merge; REVIEW_BOT_TOKEN = human account for approvals) - AGENTS.md: update token descriptions to match new roles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b38b2b13ae
commit
f73d5f471e
5 changed files with 82 additions and 220 deletions
10
.env.example
10
.env.example
|
|
@ -9,10 +9,14 @@ PRIMARY_BRANCH=main # main or master
|
|||
# PROJECT_NAME=yourproject # optional — auto-derived from CODEBERG_REPO
|
||||
|
||||
# ── Auth tokens ───────────────────────────────────────────────────────────
|
||||
# Codeberg API token (read from ~/.netrc by default, override here if needed)
|
||||
# CODEBERG_TOKEN=
|
||||
# Dev-agent token: push branches, create PRs, merge PRs.
|
||||
# Use the dedicated bot account (e.g. factory_bot / disinto_dev).
|
||||
# Branch protection: this account must be in the merge whitelist.
|
||||
CODEBERG_TOKEN=
|
||||
|
||||
# Codeberg review bot token (separate account for formal reviews)
|
||||
# Review-agent token: post review comments and submit formal approvals.
|
||||
# Use the human/admin account (e.g. johba).
|
||||
# Branch protection: this account must be in the approvals whitelist.
|
||||
REVIEW_BOT_TOKEN=
|
||||
|
||||
# ── Woodpecker CI ─────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ backlog issues (all deps closed) or orphaned in-progress issues and spawns
|
|||
- `dev/phase-test.sh` — Integration test for the phase protocol
|
||||
|
||||
**Environment variables consumed** (via `lib/env.sh` + project TOML):
|
||||
- `CODEBERG_TOKEN` — API auth for issue/PR operations
|
||||
- `CODEBERG_TOKEN` — Dev-agent token (push, PR creation, merge) — use the dedicated bot account
|
||||
- `CODEBERG_REPO`, `CODEBERG_API` — Target repository
|
||||
- `PROJECT_NAME`, `PROJECT_REPO_ROOT` — Local checkout path
|
||||
- `PRIMARY_BRANCH` — Branch to merge into (e.g. `main`, `master`)
|
||||
|
|
@ -101,7 +101,8 @@ spawns `review-pr.sh <pr-number>`.
|
|||
- `review/review-pr.sh` — Creates/reuses a tmux session (`review-{project}-{pr}`), injects PR diff, waits for Claude to write structured JSON output, posts markdown review + formal Codeberg review, auto-creates follow-up issues for pre-existing tech debt
|
||||
|
||||
**Environment variables consumed**:
|
||||
- `CODEBERG_TOKEN`, `REVIEW_BOT_TOKEN` — Separate tokens for posting reviews (branch protection requires a different user)
|
||||
- `CODEBERG_TOKEN` — Dev-agent token (must not be the same account as REVIEW_BOT_TOKEN)
|
||||
- `REVIEW_BOT_TOKEN` — Review-agent token for approvals (use human/admin account; branch protection: in approvals whitelist)
|
||||
- `CODEBERG_REPO`, `CODEBERG_API`, `PROJECT_NAME`, `PROJECT_REPO_ROOT`
|
||||
- `PRIMARY_BRANCH`, `WOODPECKER_REPO_ID`
|
||||
- `MATRIX_TOKEN`, `MATRIX_ROOM_ID`, `MATRIX_HOMESERVER`
|
||||
|
|
|
|||
|
|
@ -523,10 +523,27 @@ echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
|
|||
(CI runs again after each push — always write awaiting_ci, not awaiting_review)
|
||||
|
||||
**When you receive an \"Approved\" injection:**
|
||||
The injection includes exact API commands. Merge the PR and close the issue directly:
|
||||
\`\`\`bash
|
||||
# Merge (replace NNN with the actual PR number from the injection):
|
||||
curl -sf -X POST \\
|
||||
-H \"Authorization: token \${CODEBERG_TOKEN}\" \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
\"${API}/pulls/NNN/merge\" \\
|
||||
-d '{\"Do\":\"merge\",\"delete_branch_after_merge\":true}'
|
||||
|
||||
# Close the issue:
|
||||
curl -sf -X PATCH \\
|
||||
-H \"Authorization: token \${CODEBERG_TOKEN}\" \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
\"${API}/issues/${ISSUE}\" \\
|
||||
-d '{\"state\":\"closed\"}'
|
||||
|
||||
# Signal done:
|
||||
echo \"PHASE:done\" > \"${PHASE_FILE}\"
|
||||
\`\`\`
|
||||
The orchestrator handles the merge. You are done.
|
||||
If merge fails due to conflicts, rebase first then retry the merge.
|
||||
If merge repeatedly fails, write PHASE:needs_human.
|
||||
|
||||
**If refusing (too large, unmet dep, already done):**
|
||||
\`\`\`bash
|
||||
|
|
|
|||
|
|
@ -163,54 +163,6 @@ log() {
|
|||
printf '[%s] poll: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE"
|
||||
}
|
||||
|
||||
# HELPER: try merge, rebase if mergeable=false, then retry once
|
||||
try_merge_or_rebase() {
|
||||
local pr_num="$1" issue_num="$2" branch="$3"
|
||||
local merge_code
|
||||
|
||||
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 #${issue_num}"
|
||||
curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${issue_num}" -d '{"state":"closed"}' >/dev/null 2>&1 || true
|
||||
curl -sf -X DELETE -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/issues/${issue_num}/labels/in-progress" >/dev/null 2>&1 || true
|
||||
matrix_send "dev" "✅ PR #${pr_num} merged! Issue #${issue_num} done." 2>/dev/null || true
|
||||
ci_fix_reset "$pr_num"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Merge failed — always try rebase (Codeberg mergeable field is unreliable)
|
||||
log "PR #${pr_num} merge failed (HTTP ${merge_code}) — attempting rebase"
|
||||
matrix_send "dev" "🔀 PR #${pr_num} merge failed (${merge_code}) — auto-rebasing" 2>/dev/null || true
|
||||
local worktree="/tmp/rebase-pr-${pr_num}"
|
||||
rm -rf "$worktree"
|
||||
git -C "${PROJECT_REPO_ROOT}" fetch origin 2>/dev/null || true
|
||||
if git -C "${PROJECT_REPO_ROOT}" worktree add "$worktree" "$branch" 2>/dev/null &&
|
||||
git -C "$worktree" rebase "origin/${PRIMARY_BRANCH}" 2>/dev/null &&
|
||||
git -C "$worktree" push --force-with-lease origin "$branch" 2>/dev/null; then
|
||||
log "PR #${pr_num} rebased — CI will re-run, merge on next poll"
|
||||
git -C "${PROJECT_REPO_ROOT}" worktree remove "$worktree" 2>/dev/null || true
|
||||
else
|
||||
git -C "${PROJECT_REPO_ROOT}" worktree remove --force "$worktree" 2>/dev/null || true
|
||||
if handle_ci_exhaustion "$pr_num" "$issue_num"; then
|
||||
log "PR #${pr_num} rebase failed — CI exhausted, not spawning"
|
||||
return 1
|
||||
fi
|
||||
log "PR #${pr_num} rebase failed — spawning dev-agent to fix (attempt ${CI_FIX_ATTEMPTS}/3)"
|
||||
matrix_send "dev" "❌ PR #${pr_num} rebase failed — spawning dev-agent (attempt ${CI_FIX_ATTEMPTS}/3)" 2>/dev/null || true
|
||||
nohup "${SCRIPT_DIR}/dev-agent.sh" "$issue_num" >> "$LOGFILE" 2>&1 &
|
||||
log "started dev-agent PID $! for PR #${pr_num} (rebase fix)"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- Check if dev-agent already running ---
|
||||
if [ -f "$LOCKFILE" ]; then
|
||||
LOCK_PID=$(cat "$LOCKFILE" 2>/dev/null || echo "")
|
||||
|
|
@ -326,10 +278,9 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then
|
|||
jq -r '[.[] | select(.state == "REQUEST_CHANGES") | select(.stale == false)] | length') || true
|
||||
|
||||
if ci_passed "$CI_STATE" && [ "${HAS_APPROVE:-0}" -gt 0 ]; then
|
||||
PR_BRANCH=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/pulls/${HAS_PR}" | jq -r '.head.ref') || true
|
||||
log "PR #${HAS_PR} approved + CI green → merging"
|
||||
try_merge_or_rebase "$HAS_PR" "$ISSUE_NUM" "$PR_BRANCH"
|
||||
log "PR #${HAS_PR} approved + CI green → spawning dev-agent to merge"
|
||||
nohup "${SCRIPT_DIR}/dev-agent.sh" "$ISSUE_NUM" >> "$LOGFILE" 2>&1 &
|
||||
log "started dev-agent PID $! for issue #${ISSUE_NUM} (agent-merge)"
|
||||
exit 0
|
||||
|
||||
elif ci_passed "$CI_STATE" && [ "${HAS_CHANGES:-0}" -gt 0 ]; then
|
||||
|
|
@ -396,11 +347,12 @@ for i in $(seq 0 $(($(echo "$OPEN_PRS" | jq 'length') - 1))); do
|
|||
"${API}/pulls/${PR_NUM}/reviews" | \
|
||||
jq -r '[.[] | select(.state == "APPROVED") | select(.stale == false)] | length') || true
|
||||
|
||||
# Try merge if approved + CI green
|
||||
# Spawn agent to merge if approved + CI green
|
||||
if ci_passed "$CI_STATE" && [ "${HAS_APPROVE:-0}" -gt 0 ]; then
|
||||
log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) approved + CI green → merging"
|
||||
try_merge_or_rebase "$PR_NUM" "$STUCK_ISSUE" "$PR_BRANCH"
|
||||
continue
|
||||
log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) approved + CI green → spawning dev-agent to merge"
|
||||
nohup "${SCRIPT_DIR}/dev-agent.sh" "$STUCK_ISSUE" >> "$LOGFILE" 2>&1 &
|
||||
log "started dev-agent PID $! for stuck PR #${PR_NUM} (agent-merge)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stuck: REQUEST_CHANGES or CI failure → spawn agent
|
||||
|
|
@ -473,11 +425,10 @@ for i in $(seq 0 $((BACKLOG_COUNT - 1))); do
|
|||
jq -r '[.[] | select(.state == "REQUEST_CHANGES") | select(.stale == false)] | length') || true
|
||||
|
||||
if ci_passed "$CI_STATE" && [ "${HAS_APPROVE:-0}" -gt 0 ]; then
|
||||
EXISTING_BRANCH=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/pulls/${EXISTING_PR}" | jq -r '.head.ref') || true
|
||||
log "#${ISSUE_NUM} PR #${EXISTING_PR} approved + CI green → merging"
|
||||
try_merge_or_rebase "$EXISTING_PR" "$ISSUE_NUM" "$EXISTING_BRANCH"
|
||||
continue
|
||||
log "#${ISSUE_NUM} PR #${EXISTING_PR} approved + CI green → spawning dev-agent to merge"
|
||||
nohup "${SCRIPT_DIR}/dev-agent.sh" "$ISSUE_NUM" >> "$LOGFILE" 2>&1 &
|
||||
log "started dev-agent PID $! for issue #${ISSUE_NUM} (agent-merge)"
|
||||
exit 0
|
||||
|
||||
elif [ "${HAS_CHANGES:-0}" -gt 0 ]; then
|
||||
log "#${ISSUE_NUM} PR #${EXISTING_PR} has REQUEST_CHANGES — picking up"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# dev/phase-handler.sh — Phase callback functions for dev-agent.sh
|
||||
#
|
||||
# Source this file from dev-agent.sh after lib/agent-session.sh is loaded.
|
||||
# Defines: post_refusal_comment(), do_merge(), _on_phase_change()
|
||||
# Defines: post_refusal_comment(), _on_phase_change()
|
||||
#
|
||||
# Required globals from dev-agent.sh:
|
||||
# ISSUE, CODEBERG_TOKEN, API, CODEBERG_WEB, PROJECT_NAME, FACTORY_ROOT
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
# PRIMARY_BRANCH, SESSION_NAME, LOGFILE, ISSUE_TITLE
|
||||
# CI_POLL_TIMEOUT, MAX_CI_FIXES, MAX_REVIEW_ROUNDS, REVIEW_POLL_TIMEOUT
|
||||
# CI_RETRY_COUNT, CI_FIX_COUNT, REVIEW_ROUND, CLAIMED
|
||||
# WOODPECKER_REPO_ID, WOODPECKER_TOKEN, WOODPECKER_SERVER, REVIEW_BOT_TOKEN
|
||||
# WOODPECKER_REPO_ID, WOODPECKER_TOKEN, WOODPECKER_SERVER
|
||||
#
|
||||
# Calls back to dev-agent.sh-defined helpers:
|
||||
# cleanup_worktree(), cleanup_labels()
|
||||
|
|
@ -48,138 +48,6 @@ ${body}
|
|||
rm -f "/tmp/refusal-comment.txt" "/tmp/refusal-comment.json"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MERGE HELPER
|
||||
# =============================================================================
|
||||
do_merge() {
|
||||
local sha="$1"
|
||||
local pr="${PR_NUMBER}"
|
||||
|
||||
for _m in $(seq 1 20); do
|
||||
local ci
|
||||
ci=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/commits/${sha}/status" | jq -r '.state // "unknown"')
|
||||
[ "$ci" = "success" ] && break
|
||||
if [ "$ci" = "failure" ] || [ "$ci" = "error" ]; then
|
||||
log "CI is red before merge attempt — aborting"
|
||||
notify "PR #${pr} CI is failing; cannot merge."
|
||||
return 1
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
|
||||
# Pre-emptive rebase to avoid merge conflicts
|
||||
local mergeable
|
||||
mergeable=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/pulls/${pr}" | jq -r '.mergeable // true')
|
||||
if [ "$mergeable" = "false" ]; then
|
||||
log "PR #${pr} has merge conflicts — attempting rebase"
|
||||
local work_dir="${WORKTREE:-$REPO_ROOT}"
|
||||
if (cd "$work_dir" && git fetch origin "${PRIMARY_BRANCH}" && git rebase "origin/${PRIMARY_BRANCH}" 2>&1); then
|
||||
log "rebase succeeded — force pushing"
|
||||
(cd "$work_dir" && git push origin "${BRANCH}" --force-with-lease 2>&1) || true
|
||||
sha=$(cd "$work_dir" && git rev-parse HEAD)
|
||||
log "waiting for CI on rebased commit ${sha:0:7}"
|
||||
local r_ci
|
||||
for _r in $(seq 1 20); do
|
||||
r_ci=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/commits/${sha}/status" | jq -r '.state // "unknown"')
|
||||
[ "$r_ci" = "success" ] && break
|
||||
if [ "$r_ci" = "failure" ] || [ "$r_ci" = "error" ]; then
|
||||
log "CI failed after rebase"
|
||||
notify "PR #${pr} CI failed after rebase. Needs manual fix."
|
||||
return 1
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
else
|
||||
log "rebase failed — aborting and escalating"
|
||||
(cd "$work_dir" && git rebase --abort 2>/dev/null) || true
|
||||
notify "PR #${pr} has merge conflicts that need manual resolution."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local http_code
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/pulls/${pr}/merge" \
|
||||
-d '{"Do":"merge","delete_branch_after_merge":true}')
|
||||
|
||||
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
|
||||
log "PR #${pr} merged!"
|
||||
curl -sf -X DELETE \
|
||||
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/branches/${BRANCH}" >/dev/null 2>&1 || true
|
||||
curl -sf -X PATCH \
|
||||
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${ISSUE}" \
|
||||
-d '{"state":"closed"}' >/dev/null 2>&1 || true
|
||||
cleanup_labels
|
||||
notify_ctx \
|
||||
"✅ PR #${pr} merged! Issue #${ISSUE} done." \
|
||||
"✅ PR <a href='${CODEBERG_WEB}/pulls/${pr}'>#${pr}</a> merged! <a href='${CODEBERG_WEB}/issues/${ISSUE}'>Issue #${ISSUE}</a> done."
|
||||
agent_kill_session "$SESSION_NAME"
|
||||
cleanup_worktree
|
||||
rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE"
|
||||
exit 0
|
||||
else
|
||||
log "merge failed (HTTP ${http_code}) — attempting rebase and retry"
|
||||
local work_dir="${WORKTREE:-$REPO_ROOT}"
|
||||
if (cd "$work_dir" && git fetch origin "${PRIMARY_BRANCH}" && git rebase "origin/${PRIMARY_BRANCH}" 2>&1); then
|
||||
log "rebase succeeded — force pushing"
|
||||
(cd "$work_dir" && git push origin "${BRANCH}" --force-with-lease 2>&1) || true
|
||||
sha=$(cd "$work_dir" && git rev-parse HEAD)
|
||||
log "waiting for CI on rebased commit ${sha:0:7}"
|
||||
local r2_ci
|
||||
for _r2 in $(seq 1 20); do
|
||||
r2_ci=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/commits/${sha}/status" | jq -r '.state // "unknown"')
|
||||
[ "$r2_ci" = "success" ] && break
|
||||
if [ "$r2_ci" = "failure" ] || [ "$r2_ci" = "error" ]; then
|
||||
log "CI failed after merge-retry rebase"
|
||||
notify "PR #${pr} CI failed after rebase. Needs manual fix."
|
||||
return 1
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
# Re-approve (force push dismisses stale approvals)
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${REVIEW_BOT_TOKEN:-${CODEBERG_TOKEN}}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/pulls/${pr}/reviews" \
|
||||
-d '{"event":"APPROVED","body":"Auto-approved after rebase."}' >/dev/null 2>&1 || true
|
||||
# Retry merge
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/pulls/${pr}/merge" \
|
||||
-d '{"Do":"merge","delete_branch_after_merge":true}')
|
||||
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
|
||||
log "PR #${pr} merged after rebase!"
|
||||
notify_ctx \
|
||||
"✅ PR #${pr} merged! Issue #${ISSUE} done." \
|
||||
"✅ PR <a href='${CODEBERG_WEB}/pulls/${pr}'>#${pr}</a> merged! <a href='${CODEBERG_WEB}/issues/${ISSUE}'>Issue #${ISSUE}</a> done."
|
||||
curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${ISSUE}" -d '{"state":"closed"}' >/dev/null 2>&1 || true
|
||||
cleanup_labels
|
||||
agent_kill_session "$SESSION_NAME"
|
||||
cleanup_worktree
|
||||
rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
(cd "$work_dir" && git rebase --abort 2>/dev/null) || true
|
||||
fi
|
||||
log "merge still failing after rebase (HTTP ${http_code})"
|
||||
notify "PR #${pr} merge failed after rebase (HTTP ${http_code}). Needs human attention."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PHASE DISPATCH CALLBACK
|
||||
# =============================================================================
|
||||
|
|
@ -473,9 +341,33 @@ Instructions:
|
|||
|
||||
if [ "$VERDICT" = "APPROVE" ]; then
|
||||
REVIEW_FOUND=true
|
||||
agent_inject_into_session "$SESSION_NAME" "Approved! PR #${PR_NUMBER} has been approved by the reviewer.
|
||||
Write PHASE:done to the phase file — the orchestrator will handle the merge:
|
||||
echo \"PHASE:done\" > \"${PHASE_FILE}\""
|
||||
agent_inject_into_session "$SESSION_NAME" "Approved! PR #${PR_NUMBER} has been approved.
|
||||
|
||||
Merge the PR and close the issue directly — do NOT wait for the orchestrator:
|
||||
|
||||
# Merge the PR:
|
||||
curl -sf -X POST \\
|
||||
-H \"Authorization: token \${CODEBERG_TOKEN}\" \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
\"${API}/pulls/${PR_NUMBER}/merge\" \\
|
||||
-d '{\"Do\":\"merge\",\"delete_branch_after_merge\":true}'
|
||||
|
||||
# Close the issue:
|
||||
curl -sf -X PATCH \\
|
||||
-H \"Authorization: token \${CODEBERG_TOKEN}\" \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
\"${API}/issues/${ISSUE}\" \\
|
||||
-d '{\"state\":\"closed\"}'
|
||||
|
||||
If merge fails due to conflicts, rebase first:
|
||||
git fetch origin ${PRIMARY_BRANCH} && git rebase origin/${PRIMARY_BRANCH}
|
||||
git push --force-with-lease origin ${BRANCH}
|
||||
# Then retry the merge curl above.
|
||||
|
||||
After a successful merge write PHASE:done:
|
||||
echo \"PHASE:done\" > \"${PHASE_FILE}\"
|
||||
|
||||
If merge repeatedly fails, write PHASE:needs_human with a reason."
|
||||
break
|
||||
|
||||
elif [ "$VERDICT" = "REQUEST_CHANGES" ] || [ "$VERDICT" = "DISCUSS" ]; then
|
||||
|
|
@ -557,26 +449,23 @@ 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.
|
||||
elif [ "$phase" = "PHASE:done" ]; then
|
||||
status "phase done — merging PR #${PR_NUMBER:-?}"
|
||||
status "phase done — agent merged PR #${PR_NUMBER:-?}, cleaning up"
|
||||
|
||||
if [ -z "${PR_NUMBER:-}" ]; then
|
||||
log "ERROR: PHASE:done but no PR_NUMBER — cannot merge"
|
||||
notify "PHASE:done but no PR known — needs human attention"
|
||||
agent_kill_session "$SESSION_NAME"
|
||||
cleanup_labels
|
||||
return 1
|
||||
fi
|
||||
# Notify Matrix (agent already closed the issue and removed labels via API)
|
||||
notify_ctx \
|
||||
"✅ PR #${PR_NUMBER:-?} merged! Issue #${ISSUE} done." \
|
||||
"✅ PR <a href='${CODEBERG_WEB}/pulls/${PR_NUMBER:-?}'>#${PR_NUMBER:-?}</a> merged! <a href='${CODEBERG_WEB}/issues/${ISSUE}'>Issue #${ISSUE}</a> done."
|
||||
|
||||
MERGE_SHA=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${API}/pulls/${PR_NUMBER}" | jq -r '.head.sha') || true
|
||||
# Belt-and-suspenders: ensure in-progress label removed (idempotent)
|
||||
cleanup_labels
|
||||
|
||||
# do_merge exits 0 on success; returns 1 on failure
|
||||
do_merge "$MERGE_SHA" || true
|
||||
|
||||
# If we reach here, merge failed (do_merge returned 1)
|
||||
log "merge failed — injecting error into session"
|
||||
agent_inject_into_session "$SESSION_NAME" "Merge failed for PR #${PR_NUMBER}. The orchestrator could not merge automatically. This may be due to merge conflicts or CI. Investigate the PR state and write PHASE:needs_human if human intervention is required."
|
||||
# Local cleanup
|
||||
agent_kill_session "$SESSION_NAME"
|
||||
cleanup_worktree
|
||||
rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE"
|
||||
CLAIMED=false # Don't unclaim again in cleanup()
|
||||
|
||||
# ── PHASE: failed ───────────────────────────────────────────────────────────
|
||||
elif [ "$phase" = "PHASE:failed" ]; then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue