From ac5448323b263adb4e87cc210cd2804ab738697a Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 06:17:34 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20dev-agent=20worktree=20pushes=20to=20ori?= =?UTF-8?q?gin=20(Codeberg)=20instead=20of=20forgejo=20(local)=20=E2=80=94?= =?UTF-8?q?=20PR=20creation=20fails=20(#653)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- action/action-agent.sh | 13 ++++++++++--- dev/dev-agent.sh | 30 ++++++++++++++++++++---------- dev/phase-handler.sh | 24 ++++++++++++------------ review/review-poll.sh | 2 +- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/action/action-agent.sh b/action/action-agent.sh index 633c378..3c54396 100755 --- a/action/action-agent.sh +++ b/action/action-agent.sh @@ -203,8 +203,15 @@ fi # --- Create isolated worktree --- log "creating worktree: ${WORKTREE}" cd "${PROJECT_REPO_ROOT}" -git fetch origin "${PRIMARY_BRANCH}" 2>/dev/null || true -if ! git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" 2>&1; then + +# Determine which git remote corresponds to FORGE_URL +_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 + +git fetch "${FORGE_REMOTE}" "${PRIMARY_BRANCH}" 2>/dev/null || true +if ! git worktree add "$WORKTREE" "${FORGE_REMOTE}/${PRIMARY_BRANCH}" 2>&1; then log "ERROR: worktree creation failed" notify "failed to create worktree for #${ISSUE}" exit 1 @@ -266,7 +273,7 @@ ${PRIOR_SECTION}## Instructions ### Path A: If this action produces code changes (e.g. config updates, baselines): - You are already in an isolated worktree at: ${WORKTREE} - Create and switch to branch: git checkout -b ${BRANCH} - - Make your changes, commit, and push: git push origin ${BRANCH} + - Make your changes, commit, and push: git push ${FORGE_REMOTE} ${BRANCH} - **IMPORTANT:** The worktree is destroyed after completion. Push all results before signaling done — unpushed work will be lost. - Follow the phase protocol below — the orchestrator handles PR creation, diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index aa3e666..b90c2a4 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -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' \"\" > \"\${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 "") diff --git a/dev/phase-handler.sh b/dev/phase-handler.sh index 08ef9df..024bfc4 100644 --- a/dev/phase-handler.sh +++ b/dev/phase-handler.sh @@ -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' "" > "\${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})" diff --git a/review/review-poll.sh b/review/review-poll.sh index d21dac5..ab341bb 100755 --- a/review/review-poll.sh +++ b/review/review-poll.sh @@ -152,7 +152,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 ${pr_branch} +3. Commit your changes and push: git push ${FORGE_REMOTE:-origin} ${pr_branch} 4. Write: echo \"PHASE:awaiting_ci\" > \"${phase_file}\" 5. Stop and wait for the next CI result." fi