Merge pull request 'fix: fix: dev-agent worktree pushes to origin (Codeberg) instead of forgejo (local) — PR creation fails (#653)' (#657) from fix/issue-653 into main

This commit is contained in:
johba 2026-03-25 07:38:40 +01:00
commit 55bed9dc6f
4 changed files with 43 additions and 26 deletions

View file

@ -203,8 +203,15 @@ fi
# --- Create isolated worktree --- # --- Create isolated worktree ---
log "creating worktree: ${WORKTREE}" log "creating worktree: ${WORKTREE}"
cd "${PROJECT_REPO_ROOT}" 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" log "ERROR: worktree creation failed"
notify "failed to create worktree for #${ISSUE}" notify "failed to create worktree for #${ISSUE}"
exit 1 exit 1
@ -266,7 +273,7 @@ ${PRIOR_SECTION}## Instructions
### Path A: If this action produces code changes (e.g. config updates, baselines): ### Path A: If this action produces code changes (e.g. config updates, baselines):
- You are already in an isolated worktree at: ${WORKTREE} - You are already in an isolated worktree at: ${WORKTREE}
- Create and switch to branch: git checkout -b ${BRANCH} - 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 - **IMPORTANT:** The worktree is destroyed after completion. Push all
results before signaling done — unpushed work will be lost. results before signaling done — unpushed work will be lost.
- Follow the phase protocol below — the orchestrator handles PR creation, - Follow the phase protocol below — the orchestrator handles PR creation,

View file

@ -460,8 +460,18 @@ fi
status "setting up worktree" status "setting up worktree"
cd "$REPO_ROOT" 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 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 existing worktree if on the right branch (preserves session context)
REUSE_WORKTREE=false REUSE_WORKTREE=false
@ -470,14 +480,14 @@ if [ "$RECOVERY_MODE" = true ]; then
if [ "$WT_BRANCH" = "$BRANCH" ]; then if [ "$WT_BRANCH" = "$BRANCH" ]; then
log "reusing existing worktree (preserves session)" log "reusing existing worktree (preserves session)"
cd "$WORKTREE" 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 REUSE_WORKTREE=true
fi fi
fi fi
if [ "$REUSE_WORKTREE" = false ]; then if [ "$REUSE_WORKTREE" = false ]; then
cleanup_worktree 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" log "ERROR: worktree creation failed for recovery"
cleanup_labels cleanup_labels
exit 1 exit 1
@ -499,17 +509,17 @@ else
git checkout "${PRIMARY_BRANCH}" 2>/dev/null || true git checkout "${PRIMARY_BRANCH}" 2>/dev/null || true
fi fi
git fetch origin "${PRIMARY_BRANCH}" 2>/dev/null git fetch "${FORGE_REMOTE}" "${PRIMARY_BRANCH}" 2>/dev/null
git pull --ff-only origin "${PRIMARY_BRANCH}" 2>/dev/null || true git pull --ff-only "${FORGE_REMOTE}" "${PRIMARY_BRANCH}" 2>/dev/null || true
cleanup_worktree 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" 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 cleanup_labels
exit 1 exit 1
} }
cd "$WORKTREE" 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 git submodule update --init --recursive 2>/dev/null || true
# Symlink lib node_modules from main repo (submodule init doesn't run npm install) # 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:** **After committing and pushing your branch:**
\`\`\`bash \`\`\`bash
git push origin ${BRANCH} git push ${FORGE_REMOTE} ${BRANCH}
# Write a short summary of what you implemented: # Write a short summary of what you implemented:
printf '%s' \"<your summary>\" > \"\${SUMMARY_FILE}\" printf '%s' \"<your summary>\" > \"\${SUMMARY_FILE}\"
# Signal the orchestrator to create the PR and watch for CI: # 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 if [ "$RECOVERY_MODE" = true ]; then
# Build recovery context # 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) LAST_PHASE=$(read_phase)
rm -f "$PHASE_FILE" # Clear stale phase — new session starts clean 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 "") 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) --- # --- Build phase protocol prompt (shared across agents) ---
# Generates the phase-signaling instructions for Claude prompts. # 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) # Output: The protocol text (stdout)
build_phase_protocol_prompt() { 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_ cat <<_PHASE_PROTOCOL_EOF_
## Phase-Signaling Protocol (REQUIRED) ## Phase-Signaling Protocol (REQUIRED)
@ -135,7 +135,7 @@ SUMMARY_FILE="${_sf}"
**After committing and pushing your branch:** **After committing and pushing your branch:**
\`\`\`bash \`\`\`bash
git push origin ${_br} git push ${_remote} ${_br}
# Write a short summary of what you implemented: # Write a short summary of what you implemented:
printf '%s' "<your summary>" > "\${SUMMARY_FILE}" printf '%s' "<your summary>" > "\${SUMMARY_FILE}"
# Signal the orchestrator to create the PR and watch for CI: # Signal the orchestrator to create the PR and watch for CI:
@ -315,7 +315,7 @@ _on_phase_change() {
else else
log "ERROR: PR creation failed (HTTP ${PR_HTTP_CODE})" log "ERROR: PR creation failed (HTTP ${PR_HTTP_CODE})"
notify "failed to create PR (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 return 0
fi fi
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})" log "infra failure — retrigger CI (retry ${CI_RETRY_COUNT})"
(cd "$WORKTREE" && git commit --allow-empty \ (cd "$WORKTREE" && git commit --allow-empty \
-m "ci: retrigger after infra failure (#${ISSUE})" --no-verify 2>&1 | tail -1) -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 # 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 # Do NOT update LAST_PHASE_MTIME here — let the main loop detect the fresh mtime
touch "$PHASE_FILE" touch "$PHASE_FILE"
@ -451,7 +451,7 @@ Instructions:
1. Run ci-debug.sh failures to get the full error output. 1. Run ci-debug.sh failures to get the full error output.
2. Read the failing test file(s) — understand what the tests EXPECT. 2. Read the failing test file(s) — understand what the tests EXPECT.
3. Fix the root cause — do NOT weaken tests. 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}\" 5. Write: echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
6. Stop and wait." 6. Stop and wait."
fi fi
@ -471,7 +471,7 @@ Instructions:
PR_NUMBER="$FOUND_PR" PR_NUMBER="$FOUND_PR"
log "found PR #${PR_NUMBER}" log "found PR #${PR_NUMBER}"
else 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 return 0
fi fi
fi fi
@ -556,9 +556,9 @@ Instructions:
"${API}/issues/${ISSUE}" \ "${API}/issues/${ISSUE}" \
-d '{"state":"closed"}' >/dev/null 2>&1 || true -d '{"state":"closed"}' >/dev/null 2>&1 || true
# Pull merged primary branch and push to mirrors # 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" 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 mirror_push
printf 'PHASE:done\n' > "$PHASE_FILE" printf 'PHASE:done\n' > "$PHASE_FILE"
elif [ "$_merge_rc" -ne 2 ]; then 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). 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: Rebase onto ${PRIMARY_BRANCH} and push:
git fetch origin ${PRIMARY_BRANCH} && git rebase origin/${PRIMARY_BRANCH} git fetch ${FORGE_REMOTE:-origin} ${PRIMARY_BRANCH} && git rebase ${FORGE_REMOTE:-origin}/${PRIMARY_BRANCH}
git push --force-with-lease origin ${BRANCH} git push --force-with-lease ${FORGE_REMOTE:-origin} ${BRANCH}
echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\" echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
Do NOT merge or close the issue — the orchestrator handles that after CI passes. Do NOT merge or close the issue — the orchestrator handles that after CI passes.
@ -590,7 +590,7 @@ ${REVIEW_TEXT}
Instructions: Instructions:
1. Address each piece of feedback carefully. 1. Address each piece of feedback carefully.
2. Run lint and tests when done. 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}\" 4. Write: echo \"PHASE:awaiting_ci\" > \"${PHASE_FILE}\"
5. Stop and wait for the next CI result." 5. Stop and wait for the next CI result."
log "review REQUEST_CHANGES received (round ${REVIEW_ROUND})" log "review REQUEST_CHANGES received (round ${REVIEW_ROUND})"

View file

@ -152,7 +152,7 @@ ${review_text}
Instructions: Instructions:
1. Address each piece of feedback carefully. 1. Address each piece of feedback carefully.
2. Run lint and tests when done. 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}\" 4. Write: echo \"PHASE:awaiting_ci\" > \"${phase_file}\"
5. Stop and wait for the next CI result." 5. Stop and wait for the next CI result."
fi fi