fix: remove safe worktrees before reassessing remote delete ownership (#851)
Post-merge cleanup previously continued past ownership-blocked remote deletes, which skipped independently safe local worktree removal when the only block was worktree_binding. Remove the clean owned worktree first, reassess ownership, then delete the remote branch only if still safe. Preserve fail-closed protection for dirty/foreign ownership categories. Closes #851 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -566,6 +566,10 @@ def build_pr_cleanup_entry(
|
||||
worktree_state=worktree_state,
|
||||
active_lock=active_lock,
|
||||
)
|
||||
planned = plan_cleanup_execution_order(
|
||||
remote_assessment=remote,
|
||||
local_assessment=local,
|
||||
)
|
||||
return {
|
||||
"pr_number": pr_number,
|
||||
"issue_number": issue_number,
|
||||
@@ -576,9 +580,63 @@ def build_pr_cleanup_entry(
|
||||
"merged": merged,
|
||||
"remote_branch": remote,
|
||||
"local_worktree": local,
|
||||
# #851: dry-run and execute share the same lifecycle order description.
|
||||
"planned_execution_order": planned,
|
||||
}
|
||||
|
||||
|
||||
def plan_cleanup_execution_order(
|
||||
*,
|
||||
remote_assessment: dict[str, Any] | None,
|
||||
local_assessment: dict[str, Any] | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Describe independent worktree-then-reassess-then-remote cleanup order (#851).
|
||||
|
||||
Remote ownership protection remains fail-closed at execute time. A worktree
|
||||
that is independently safe to remove is never skipped merely because remote
|
||||
deletion may be blocked by that same ``worktree_binding``.
|
||||
"""
|
||||
remote = remote_assessment or {}
|
||||
local = local_assessment or {}
|
||||
steps: list[dict[str, Any]] = []
|
||||
worktree_safe = bool(local.get("safe_to_remove_worktree"))
|
||||
remote_safe = bool(remote.get("safe_to_delete_remote"))
|
||||
|
||||
if worktree_safe:
|
||||
steps.append(
|
||||
{
|
||||
"action": "remove_local_worktree",
|
||||
"reason": "independently_safe_to_remove",
|
||||
"phase": 1,
|
||||
}
|
||||
)
|
||||
if remote_safe:
|
||||
if worktree_safe:
|
||||
steps.append(
|
||||
{
|
||||
"action": "reassess_branch_ownership",
|
||||
"reason": "after_worktree_removal_clear_worktree_binding",
|
||||
"phase": 2,
|
||||
}
|
||||
)
|
||||
steps.append(
|
||||
{
|
||||
"action": "delete_remote_branch",
|
||||
"reason": "only_if_independently_safe_after_reassessment",
|
||||
"phase": 3,
|
||||
}
|
||||
)
|
||||
else:
|
||||
steps.append(
|
||||
{
|
||||
"action": "delete_remote_branch",
|
||||
"reason": "safe_to_delete_and_no_independent_worktree_removal",
|
||||
"phase": 1,
|
||||
}
|
||||
)
|
||||
return steps
|
||||
|
||||
|
||||
def build_reconciliation_report(
|
||||
*,
|
||||
project_root: str,
|
||||
|
||||
Reference in New Issue
Block a user