fix: dev-agent worktree pushes to origin (Codeberg) instead of forgejo (local) — PR creation fails (#653)

Detect which git remote matches FORGE_URL by comparing the host portion
of FORGE_URL against remote push URLs.  Store the result in FORGE_REMOTE
(defaults to "origin" when no match — preserving existing behavior for
Codeberg-direct setups).

Replace every hardcoded "origin" in fetch, push, worktree-add, and
prompt-injection commands across:
- dev/dev-agent.sh (worktree setup, phase protocol prompt)
- dev/phase-handler.sh (CI retrigger, review feedback, rebase instructions)
- review/review-poll.sh (review feedback injection)
- action/action-agent.sh (worktree setup, push instructions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-25 06:17:34 +00:00
parent 0e5090bd51
commit ac5448323b
4 changed files with 43 additions and 26 deletions

View file

@ -460,8 +460,18 @@ fi
status "setting up worktree"
cd "$REPO_ROOT"
# Determine which git remote corresponds to FORGE_URL.
# When the forge is local Forgejo (not Codeberg), the remote is typically named
# "forgejo" rather than "origin". Matching by host ensures pushes target the
# correct forge regardless of remote naming conventions.
_forge_host=$(echo "$FORGE_URL" | sed 's|https\?://||; s|/.*||')
FORGE_REMOTE=$(git remote -v | awk -v host="$_forge_host" '$2 ~ host && /\(push\)/ {print $1; exit}')
FORGE_REMOTE="${FORGE_REMOTE:-origin}"
export FORGE_REMOTE # used by phase-handler.sh
log "forge remote: ${FORGE_REMOTE} (FORGE_URL=${FORGE_URL})"
if [ "$RECOVERY_MODE" = true ]; then
git fetch origin "$BRANCH" 2>/dev/null
git fetch "${FORGE_REMOTE}" "$BRANCH" 2>/dev/null
# Reuse existing worktree if on the right branch (preserves session context)
REUSE_WORKTREE=false
@ -470,14 +480,14 @@ if [ "$RECOVERY_MODE" = true ]; then
if [ "$WT_BRANCH" = "$BRANCH" ]; then
log "reusing existing worktree (preserves session)"
cd "$WORKTREE"
git pull --ff-only origin "$BRANCH" 2>/dev/null || git reset --hard "origin/${BRANCH}" 2>/dev/null || true
git pull --ff-only "${FORGE_REMOTE}" "$BRANCH" 2>/dev/null || git reset --hard "${FORGE_REMOTE}/${BRANCH}" 2>/dev/null || true
REUSE_WORKTREE=true
fi
fi
if [ "$REUSE_WORKTREE" = false ]; then
cleanup_worktree
git worktree add "$WORKTREE" "origin/${BRANCH}" -B "$BRANCH" 2>&1 || {
git worktree add "$WORKTREE" "${FORGE_REMOTE}/${BRANCH}" -B "$BRANCH" 2>&1 || {
log "ERROR: worktree creation failed for recovery"
cleanup_labels
exit 1
@ -499,17 +509,17 @@ else
git checkout "${PRIMARY_BRANCH}" 2>/dev/null || true
fi
git fetch origin "${PRIMARY_BRANCH}" 2>/dev/null
git pull --ff-only origin "${PRIMARY_BRANCH}" 2>/dev/null || true
git fetch "${FORGE_REMOTE}" "${PRIMARY_BRANCH}" 2>/dev/null
git pull --ff-only "${FORGE_REMOTE}" "${PRIMARY_BRANCH}" 2>/dev/null || true
cleanup_worktree
git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" -B "$BRANCH" 2>&1 || {
git worktree add "$WORKTREE" "${FORGE_REMOTE}/${PRIMARY_BRANCH}" -B "$BRANCH" 2>&1 || {
log "ERROR: worktree creation failed"
git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" -B "$BRANCH" 2>&1 | while read -r wt_line; do log " $wt_line"; done || true
git worktree add "$WORKTREE" "${FORGE_REMOTE}/${PRIMARY_BRANCH}" -B "$BRANCH" 2>&1 | while read -r wt_line; do log " $wt_line"; done || true
cleanup_labels
exit 1
}
cd "$WORKTREE"
git checkout -B "$BRANCH" "origin/${PRIMARY_BRANCH}" 2>/dev/null
git checkout -B "$BRANCH" "${FORGE_REMOTE}/${PRIMARY_BRANCH}" 2>/dev/null
git submodule update --init --recursive 2>/dev/null || true
# Symlink lib node_modules from main repo (submodule init doesn't run npm install)
@ -550,7 +560,7 @@ SUMMARY_FILE=\"${IMPL_SUMMARY_FILE}\"
**After committing and pushing your branch:**
\`\`\`bash
git push origin ${BRANCH}
git push ${FORGE_REMOTE} ${BRANCH}
# Write a short summary of what you implemented:
printf '%s' \"<your summary>\" > \"\${SUMMARY_FILE}\"
# Signal the orchestrator to create the PR and watch for CI:
@ -604,7 +614,7 @@ write_compact_context "$PHASE_FILE" "$PHASE_PROTOCOL_INSTRUCTIONS"
if [ "$RECOVERY_MODE" = true ]; then
# Build recovery context
GIT_DIFF_STAT=$(git -C "$WORKTREE" diff "origin/${PRIMARY_BRANCH}..HEAD" --stat 2>/dev/null | head -20 || echo "(no diff)")
GIT_DIFF_STAT=$(git -C "$WORKTREE" diff "${FORGE_REMOTE}/${PRIMARY_BRANCH}..HEAD" --stat 2>/dev/null | head -20 || echo "(no diff)")
LAST_PHASE=$(read_phase)
rm -f "$PHASE_FILE" # Clear stale phase — new session starts clean
CI_RESULT=$(cat "/tmp/ci-result-${PROJECT_NAME}-${ISSUE}.txt" 2>/dev/null || echo "")

View file

@ -114,10 +114,10 @@ ${tmux_output}
# --- Build phase protocol prompt (shared across agents) ---
# Generates the phase-signaling instructions for Claude prompts.
# Args: phase_file summary_file branch
# Args: phase_file summary_file branch [remote]
# Output: The protocol text (stdout)
build_phase_protocol_prompt() {
local _pf="$1" _sf="$2" _br="$3"
local _pf="$1" _sf="$2" _br="$3" _remote="${4:-${FORGE_REMOTE:-origin}}"
cat <<_PHASE_PROTOCOL_EOF_
## Phase-Signaling Protocol (REQUIRED)
@ -135,7 +135,7 @@ SUMMARY_FILE="${_sf}"
**After committing and pushing your branch:**
\`\`\`bash
git push origin ${_br}
git push ${_remote} ${_br}
# Write a short summary of what you implemented:
printf '%s' "<your summary>" > "\${SUMMARY_FILE}"
# Signal the orchestrator to create the PR and watch for CI:
@ -315,7 +315,7 @@ _on_phase_change() {
else
log "ERROR: PR creation failed (HTTP ${PR_HTTP_CODE})"
notify "failed to create PR (HTTP ${PR_HTTP_CODE})"
agent_inject_into_session "$SESSION_NAME" "ERROR: Could not create PR (HTTP ${PR_HTTP_CODE}). Check branch was pushed: git push origin ${BRANCH}. Then write PHASE:awaiting_ci again."
agent_inject_into_session "$SESSION_NAME" "ERROR: Could not create PR (HTTP ${PR_HTTP_CODE}). Check branch was pushed: git push ${FORGE_REMOTE:-origin} ${BRANCH}. Then write PHASE:awaiting_ci again."
return 0
fi
fi
@ -398,7 +398,7 @@ Write PHASE:awaiting_review to the phase file, then stop and wait for review fee
log "infra failure — retrigger CI (retry ${CI_RETRY_COUNT})"
(cd "$WORKTREE" && git commit --allow-empty \
-m "ci: retrigger after infra failure (#${ISSUE})" --no-verify 2>&1 | tail -1)
(cd "$WORKTREE" && git push origin "$BRANCH" --force 2>&1 | tail -3)
(cd "$WORKTREE" && git push "${FORGE_REMOTE:-origin}" "$BRANCH" --force 2>&1 | tail -3)
# Touch phase file so we recheck CI on the new SHA
# Do NOT update LAST_PHASE_MTIME here — let the main loop detect the fresh mtime
touch "$PHASE_FILE"
@ -451,7 +451,7 @@ Instructions:
1. Run ci-debug.sh failures to get the full error output.
2. Read the failing test file(s) — understand what the tests EXPECT.
3. Fix the root cause — do NOT weaken tests.
4. Commit your fix and push: git push origin ${BRANCH}
4. Commit your fix and push: git push ${FORGE_REMOTE:-origin} ${BRANCH}
5. Write: echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
6. Stop and wait."
fi
@ -471,7 +471,7 @@ Instructions:
PR_NUMBER="$FOUND_PR"
log "found PR #${PR_NUMBER}"
else
agent_inject_into_session "$SESSION_NAME" "ERROR: Cannot find open PR for branch ${BRANCH}. Did you push? Verify with git status and git push origin ${BRANCH}, then write PHASE:awaiting_ci."
agent_inject_into_session "$SESSION_NAME" "ERROR: Cannot find open PR for branch ${BRANCH}. Did you push? Verify with git status and git push ${FORGE_REMOTE:-origin} ${BRANCH}, then write PHASE:awaiting_ci."
return 0
fi
fi
@ -556,9 +556,9 @@ Instructions:
"${API}/issues/${ISSUE}" \
-d '{"state":"closed"}' >/dev/null 2>&1 || true
# Pull merged primary branch and push to mirrors
git -C "$PROJECT_REPO_ROOT" fetch origin "$PRIMARY_BRANCH" 2>/dev/null || true
git -C "$PROJECT_REPO_ROOT" fetch "${FORGE_REMOTE:-origin}" "$PRIMARY_BRANCH" 2>/dev/null || true
git -C "$PROJECT_REPO_ROOT" checkout "$PRIMARY_BRANCH" 2>/dev/null || true
git -C "$PROJECT_REPO_ROOT" pull --ff-only origin "$PRIMARY_BRANCH" 2>/dev/null || true
git -C "$PROJECT_REPO_ROOT" pull --ff-only "${FORGE_REMOTE:-origin}" "$PRIMARY_BRANCH" 2>/dev/null || true
mirror_push
printf 'PHASE:done\n' > "$PHASE_FILE"
elif [ "$_merge_rc" -ne 2 ]; then
@ -566,8 +566,8 @@ Instructions:
agent_inject_into_session "$SESSION_NAME" "Approved! PR #${PR_NUMBER} has been approved, but the merge failed (likely conflicts).
Rebase onto ${PRIMARY_BRANCH} and push:
git fetch origin ${PRIMARY_BRANCH} && git rebase origin/${PRIMARY_BRANCH}
git push --force-with-lease origin ${BRANCH}
git fetch ${FORGE_REMOTE:-origin} ${PRIMARY_BRANCH} && git rebase ${FORGE_REMOTE:-origin}/${PRIMARY_BRANCH}
git push --force-with-lease ${FORGE_REMOTE:-origin} ${BRANCH}
echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
Do NOT merge or close the issue — the orchestrator handles that after CI passes.
@ -590,7 +590,7 @@ ${REVIEW_TEXT}
Instructions:
1. Address each piece of feedback carefully.
2. Run lint and tests when done.
3. Commit your changes and push: git push origin ${BRANCH}
3. Commit your changes and push: git push ${FORGE_REMOTE:-origin} ${BRANCH}
4. Write: echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
5. Stop and wait for the next CI result."
log "review REQUEST_CHANGES received (round ${REVIEW_ROUND})"