fix: handle already-merged PRs + retry worktree config.lock
Some checks are pending
CI / lint-and-test (push) Waiting to run

Two fixes for the 18-PR merge blockage:

1. When cherry-pick returns "already merged" (all commits empty because
   content is already on main), close the PR directly instead of trying
   to push the stale branch SHA to main. The branch ref points at old
   commits that aren't descendants of current main, so the push would
   always fail as non-fast-forward.

2. Retry worktree add once with jittered delay when config.lock
   contention occurs from parallel domain merges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
m3taversal 2026-04-15 16:57:28 +01:00
parent ff357c4bbc
commit f38b1e3c01

View file

@ -318,6 +318,10 @@ async def _cherry_pick_onto_main(branch: str) -> tuple[bool, str]:
# Delete stale local branch if it exists from a previous failed attempt
await _git("branch", "-D", clean_branch)
rc, out = await _git("worktree", "add", "-b", clean_branch, worktree_path, "origin/main")
if rc != 0 and "could not lock config" in out:
await asyncio.sleep(random.uniform(0.5, 2.0))
await _git("branch", "-D", clean_branch)
rc, out = await _git("worktree", "add", "-b", clean_branch, worktree_path, "origin/main")
if rc != 0:
return False, f"worktree add failed: {out}"
@ -1456,6 +1460,25 @@ async def _merge_domain_queue(conn, domain: str) -> tuple[int, int]:
failed += 1
continue
# Content already on main — close PR, skip push, clean up branch.
# Cherry-pick returns "already merged" when all commits are empty.
# The branch ref still points at old commits (not a descendant of main),
# so pushing branch_sha:main would fail as non-fast-forward.
if "already" in pick_msg.lower():
conn.execute(
"UPDATE prs SET status = 'merged', merged_at = datetime('now'), last_error = NULL WHERE number = ?",
(pr_num,),
)
db.audit(conn, "merge", "merged", json.dumps({"pr": pr_num, "branch": branch, "note": "content already on main"}))
leo_token = get_agent_token("leo")
await forgejo_api("POST", repo_path(f"issues/{pr_num}/comments"),
{"body": f"Content already on main — closing.\nBranch: `{branch}`"})
await forgejo_api("PATCH", repo_path(f"pulls/{pr_num}"), {"state": "closed"}, token=leo_token)
await _delete_remote_branch(branch)
logger.info("PR #%d already merged (content on main), closed", pr_num)
succeeded += 1
continue
# Local ff-push: cherry-picked branch is a descendant of origin/main.
# Regular push = fast-forward. Non-ff rejected by default (same safety).
# --force-with-lease removed: Forgejo categorically blocks it on protected branches.