fix: preserve git worktree branch with pending changes for plan apply #11130

Closed
HAL9000 wants to merge 2 commits from bugfix/11121-fix-cleanup_stale-preserve-meaningful-changes into master
Owner

Summary

  • Fixes cleanup_stale destroying the git worktree branch during re-invoked plan execute, which caused plan apply to find zero artifacts
  • Adds an allow_destroy_with_changes parameter (default False) that inspects the branch diff against HEAD before destruction; branches with insertions/modifications are preserved for plan apply
  • Plan cancel path explicitly passes True to maintain aggressive cleanup behavior

Changes

File Change
src/cleveragents/infrastructure/sandbox/git_worktree.py Added allow_destroy_with_changes param with diff inspection; branches with pending work are preserved
src/cleveragents/cli/commands/plan.py Pass True in _cleanup_sandbox_for_plan() (used by plan cancel) for unconditional cleanup
## Summary - Fixes `cleanup_stale` destroying the git worktree branch during re-invoked `plan execute`, which caused `plan apply` to find zero artifacts - Adds an `allow_destroy_with_changes` parameter (default `False`) that inspects the branch diff against HEAD before destruction; branches with insertions/modifications are preserved for `plan apply` - Plan cancel path explicitly passes `True` to maintain aggressive cleanup behavior ## Changes | File | Change | |---|---| | `src/cleveragents/infrastructure/sandbox/git_worktree.py` | Added `allow_destroy_with_changes` param with diff inspection; branches with pending work are preserved | | `src/cleveragents/cli/commands/plan.py` | Pass `True` in `_cleanup_sandbox_for_plan()` (used by plan cancel) for unconditional cleanup |
fix: preserve stale branch with pending changes for plan apply
Some checks failed
CI / benchmark-publish (pull_request) Has been skipped
CI / quality (pull_request) Successful in 43s
CI / lint (pull_request) Failing after 49s
CI / security (pull_request) Successful in 1m5s
CI / typecheck (pull_request) Successful in 1m12s
CI / integration_tests (pull_request) Successful in 3m17s
CI / push-validation (pull_request) Successful in 47s
CI / helm (pull_request) Successful in 58s
CI / build (pull_request) Successful in 1m13s
CI / benchmark-regression (pull_request) Failing after 2m3s
CI / unit_tests (pull_request) Successful in 6m13s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 5m54s
CI / status-check (pull_request) Failing after 4s
947ac0b08a
When cleanup_stable() was re-invoked during plan execute recovery,
it unconditionally destroyed the worktree branch even if it contained
committed work waiting for plan apply to merge, causing plan apply to
find zero artifacts.

Fix: add allow_destroy_with_changes parameter (default False) to
cleanup_stale(). When False, inspect the branch diff against HEAD before
destroying and preserve branches with insertions or modifications so that
plan apply retains access to the work products. Only truly empty
or deletions-only branches are treated as stale.

- git_worktree.py: Add allow_destroy_with_changes param with diff inspection
- plan.py: Pass True explicitly in _cleanup_sandbox_for_plan (plan cancel)
fix(lint): replace EN DASH with hyphen-minus in comment
Some checks failed
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 1m10s
CI / quality (pull_request) Successful in 1m13s
CI / security (pull_request) Successful in 1m13s
CI / typecheck (pull_request) Successful in 1m28s
CI / benchmark-regression (pull_request) Failing after 55s
CI / push-validation (pull_request) Successful in 28s
CI / helm (pull_request) Successful in 38s
CI / build (pull_request) Successful in 43s
CI / integration_tests (pull_request) Successful in 4m18s
CI / e2e_tests (pull_request) Successful in 3m49s
CI / unit_tests (pull_request) Successful in 5m46s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
f7c297c620
HAL9001 left a comment

Review Summary

This PR attempts to fix the same regression as issue #11121cleanup_stale destroying the git worktree branch before plan apply can merge artifacts. The intent is correct, but there are multiple blocking issues that must be resolved before this can be approved.

CI Status

  • lint (required gate) — FAILING: A 3-space indent on line 167 of git_worktree.py (the # -- class helpers --- section comment) triggers a ruff format violation. This was introduced in commit 947ac0b0.
  • benchmark-regression — Failing, though this may be pre-existing and unrelated to this PR.
  • typecheck, security, quality, unit_tests, integration_tests, e2e_tests — All passing.

Blocking Issues Found

  1. Lint failure — Line 167 has 3-space indent instead of 4 on the # -- class helpers comment. The second lint-fix commit (f7c297c6) fixed an EN DASH but left this indent error in place.

  2. Competing open PR — PR #11132 (Fix cleanup_stale to preserve branches with commits beyond HEAD) is already open for the same issue #11121 and is further along (includes CHANGELOG, CONTRIBUTORS, tests). Before continuing with this PR, the relationship between the two must be clarified.

  3. No BDD tests added — The allow_destroy_with_changes parameter and preservation logic have no new Behave scenarios. The acceptance criteria in issue #11121 require a @tdd_issue_<N> regression test (from the companion TDD issue #11120). The existing sandbox_reexecute_cleanup.feature does not cover the new allow_destroy_with_changes=False preservation path.

  4. No CHANGELOG entry — The project CONTRIBUTING rules require a CHANGELOG.md entry per commit describing the change for users.

  5. No ISSUES CLOSED in commit footer — The commit message for 947ac0b0 has no ISSUES CLOSED: #N footer as required by the commit quality rules.

  6. No issue reference in PR description — The PR body has no Closes #N, Fixes #N, or Refs #N keyword. Every PR must reference its issue.

  7. No milestone or Type/ label — The PR has no milestone assigned and no Type/Bug label.

  8. Branch name and commit message mismatch with issue Metadata — Issue #11121 Metadata specifies:

    • Branch: bugfix/m3-cleanup-stale-destroys-execute-output
    • Commit Message: fix(plan): guard cleanup_stale against execute/complete plans awaiting apply
      This PR uses a different branch name and commit first line.
  9. Logic concern — The has_changes detection includes "deletion" as a meaningful-change keyword (preserving deletion-only branches), but the comment on line 248 says "Branch has only deletions or is empty — safe to clean up." These are contradictory. See inline comment.

  10. Dead code — Lines 237–238 (if not diff_text: has_changes = False) are dead code since bool(diff_text) and any(...) already evaluates to False when diff_text is empty.

Suggested Path Forward

Recommend coordinating with the author of PR #11132. If this PR is meant to supersede #11132, close #11132 first and address all the blocking issues above. If both approaches are being evaluated, pick one and ensure it passes all gates before requesting review.

## Review Summary This PR attempts to fix the same regression as issue #11121 — `cleanup_stale` destroying the git worktree branch before `plan apply` can merge artifacts. The intent is correct, but there are multiple blocking issues that must be resolved before this can be approved. ### CI Status - ❌ `lint` (required gate) — **FAILING**: A 3-space indent on line 167 of `git_worktree.py` (the `# -- class helpers ---` section comment) triggers a ruff format violation. This was introduced in commit `947ac0b0`. - ❌ `benchmark-regression` — Failing, though this may be pre-existing and unrelated to this PR. - ✅ `typecheck`, `security`, `quality`, `unit_tests`, `integration_tests`, `e2e_tests` — All passing. ### Blocking Issues Found 1. **Lint failure** — Line 167 has 3-space indent instead of 4 on the `# -- class helpers` comment. The second lint-fix commit (`f7c297c6`) fixed an EN DASH but left this indent error in place. 2. **Competing open PR** — PR #11132 (`Fix cleanup_stale to preserve branches with commits beyond HEAD`) is already open for the same issue #11121 and is further along (includes CHANGELOG, CONTRIBUTORS, tests). Before continuing with this PR, the relationship between the two must be clarified. 3. **No BDD tests added** — The `allow_destroy_with_changes` parameter and preservation logic have no new Behave scenarios. The acceptance criteria in issue #11121 require a `@tdd_issue_<N>` regression test (from the companion TDD issue #11120). The existing `sandbox_reexecute_cleanup.feature` does not cover the new `allow_destroy_with_changes=False` preservation path. 4. **No CHANGELOG entry** — The project CONTRIBUTING rules require a CHANGELOG.md entry per commit describing the change for users. 5. **No `ISSUES CLOSED` in commit footer** — The commit message for `947ac0b0` has no `ISSUES CLOSED: #N` footer as required by the commit quality rules. 6. **No issue reference in PR description** — The PR body has no `Closes #N`, `Fixes #N`, or `Refs #N` keyword. Every PR must reference its issue. 7. **No milestone or Type/ label** — The PR has no milestone assigned and no `Type/Bug` label. 8. **Branch name and commit message mismatch with issue Metadata** — Issue #11121 Metadata specifies: - `Branch: bugfix/m3-cleanup-stale-destroys-execute-output` - `Commit Message: fix(plan): guard cleanup_stale against execute/complete plans awaiting apply` This PR uses a different branch name and commit first line. 9. **Logic concern** — The `has_changes` detection includes `"deletion"` as a meaningful-change keyword (preserving deletion-only branches), but the comment on line 248 says *"Branch has only deletions or is empty — safe to clean up."* These are contradictory. See inline comment. 10. **Dead code** — Lines 237–238 (`if not diff_text: has_changes = False`) are dead code since `bool(diff_text) and any(...)` already evaluates to `False` when `diff_text` is empty. ### Suggested Path Forward Recommend coordinating with the author of PR #11132. If this PR is meant to supersede #11132, close #11132 first and address all the blocking issues above. If both approaches are being evaluated, pick one and ensure it passes all gates before requesting review.
Owner

BLOCKING — Commit message and branch name do not match issue Metadata

Issue #11121 Metadata section specifies:

  • Branch: bugfix/m3-cleanup-stale-destroys-execute-output
  • Commit Message: fix(plan): guard cleanup_stale against execute/complete plans awaiting apply

This PR uses branch bugfix/11121-fix-cleanup_stale-preserve-meaningful-changes and commit first line fix: preserve stale branch with pending changes for plan apply.

Per CONTRIBUTING rules, the commit first line must be verbatim from the issue Metadata section when one is prescribed. The branch name must match the Branch field exactly.

Additionally:

  • The commit footer is missing ISSUES CLOSED: #11121 (or #11120 — whichever is the bug issue being fixed)
  • CHANGELOG.md was not updated
  • The PR description has no Closes #N keyword

To fix:

  1. Update the commit message first line to exactly: fix(plan): guard cleanup_stale against execute/complete plans awaiting apply
  2. Add ISSUES CLOSED: #11121 to the commit footer
  3. Add a CHANGELOG.md entry under [Unreleased] / ### Fixed
  4. Add Closes #11121 to the PR description
**BLOCKING — Commit message and branch name do not match issue Metadata** Issue #11121 Metadata section specifies: - **Branch**: `bugfix/m3-cleanup-stale-destroys-execute-output` - **Commit Message**: `fix(plan): guard cleanup_stale against execute/complete plans awaiting apply` This PR uses branch `bugfix/11121-fix-cleanup_stale-preserve-meaningful-changes` and commit first line `fix: preserve stale branch with pending changes for plan apply`. Per CONTRIBUTING rules, the commit first line must be verbatim from the issue Metadata section when one is prescribed. The branch name must match the Branch field exactly. Additionally: - The commit footer is missing `ISSUES CLOSED: #11121` (or `#11120` — whichever is the bug issue being fixed) - CHANGELOG.md was not updated - The PR description has no `Closes #N` keyword To fix: 1. Update the commit message first line to exactly: `fix(plan): guard cleanup_stale against execute/complete plans awaiting apply` 2. Add `ISSUES CLOSED: #11121` to the commit footer 3. Add a CHANGELOG.md entry under `[Unreleased] / ### Fixed` 4. Add `Closes #11121` to the PR description
Owner

BLOCKING — Indentation error causing lint failure

Line 167 (after patch offset) has 3 spaces instead of 4 on the # -- class helpers section comment:

   # -- class helpers -------------------------------------------------------

Should be:

    # -- class helpers -------------------------------------------------------

This one-space difference causes ruff to fail the lint CI gate, which is a required-for-merge check. The second commit (f7c297c6: fix(lint): replace EN DASH with hyphen-minus in comment) fixed the EN DASH character but did not fix this indentation regression.

Fix: add the missing leading space to restore the 4-space indent.

**BLOCKING — Indentation error causing lint failure** Line 167 (after patch offset) has 3 spaces instead of 4 on the `# -- class helpers` section comment: ```python # -- class helpers ------------------------------------------------------- ``` Should be: ```python # -- class helpers ------------------------------------------------------- ``` This one-space difference causes `ruff` to fail the `lint` CI gate, which is a required-for-merge check. The second commit (`f7c297c6: fix(lint): replace EN DASH with hyphen-minus in comment`) fixed the EN DASH character but did not fix this indentation regression. Fix: add the missing leading space to restore the 4-space indent.
Owner

BLOCKING — No BDD regression test for new allow_destroy_with_changes behavior

The allow_destroy_with_changes=False preservation path (the core of this fix) has no Behave scenario covering it. The acceptance criteria in issue #11121 require the companion TDD test from issue #11120 to pass with @tdd_expected_fail removed. That test scenario asserts the branch is preserved after a re-invoked execute — but no such scenario exists in this PR.

What is needed:

  • A new scenario in an appropriate .feature file (e.g., sandbox_reexecute_cleanup.feature or a new dedicated file) that:
    1. Creates a branch with at least one insertion relative to HEAD
    2. Calls cleanup_stale(...) without allow_destroy_with_changes=True
    3. Asserts the branch was preserved (return False) because it has meaningful changes
  • The @tdd_issue_11121 tag (or @tdd_issue_<N> for whichever is the correct bug issue number)
  • Verify the existing scenario cleanup_stale removes existing branch and worktree for srec still passes (empty branches should still be cleaned up)

Without this test the coverage gate may drop below 97% and there is no regression guard for the fix.

**BLOCKING — No BDD regression test for new `allow_destroy_with_changes` behavior** The `allow_destroy_with_changes=False` preservation path (the core of this fix) has no Behave scenario covering it. The acceptance criteria in issue #11121 require the companion TDD test from issue #11120 to pass with `@tdd_expected_fail` removed. That test scenario asserts the branch is preserved after a re-invoked execute — but no such scenario exists in this PR. What is needed: - A new scenario in an appropriate `.feature` file (e.g., `sandbox_reexecute_cleanup.feature` or a new dedicated file) that: 1. Creates a branch with at least one insertion relative to HEAD 2. Calls `cleanup_stale(...)` without `allow_destroy_with_changes=True` 3. Asserts the branch was preserved (`return False`) because it has meaningful changes - The `@tdd_issue_11121` tag (or `@tdd_issue_<N>` for whichever is the correct bug issue number) - Verify the existing scenario `cleanup_stale removes existing branch and worktree for srec` still passes (empty branches should still be cleaned up) Without this test the coverage gate may drop below 97% and there is no regression guard for the fix.
Owner

Suggestion — Contradictory logic and dead code in has_changes detection

The current logic:

has_changes = bool(diff_text) and any(
    keyword in diff_text
    for keyword in ("insertion", "deletion")
)
# Also treat empty diffs (no files changed at all) as preserved.
if not diff_text:
    has_changes = False

Two issues:

  1. Dead code: if not diff_text: has_changes = False is unreachable — when diff_text is empty, bool(diff_text) is already False, so has_changes will already be False from the any(...) expression. This branch never executes.

  2. Contradictory comment: Line 248 says "Branch has only deletions or is empty — safe to clean up." But "deletion" is included as a keyword in the any() check above, meaning deletion-only branches set has_changes = True and are preserved, not cleaned up. If the intent is to preserve deletion-only branches, the comment is wrong. If the intent is to clean them up, remove "deletion" from the keyword list.

Recommendation: decide which behaviour is correct (preserve deletion-only branches or not) and update the code and comment to be consistent.

**Suggestion — Contradictory logic and dead code in `has_changes` detection** The current logic: ```python has_changes = bool(diff_text) and any( keyword in diff_text for keyword in ("insertion", "deletion") ) # Also treat empty diffs (no files changed at all) as preserved. if not diff_text: has_changes = False ``` Two issues: 1. **Dead code**: `if not diff_text: has_changes = False` is unreachable — when `diff_text` is empty, `bool(diff_text)` is already `False`, so `has_changes` will already be `False` from the `any(...)` expression. This branch never executes. 2. **Contradictory comment**: Line 248 says *"Branch has only deletions or is empty — safe to clean up."* But `"deletion"` is included as a keyword in the `any()` check above, meaning deletion-only branches set `has_changes = True` and are **preserved**, not cleaned up. If the intent is to preserve deletion-only branches, the comment is wrong. If the intent is to clean them up, remove `"deletion"` from the keyword list. Recommendation: decide which behaviour is correct (preserve deletion-only branches or not) and update the code and comment to be consistent.
Owner

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

--- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
Member

close as it dup with #11127

close as it dup with #11127
hurui200320 closed this pull request 2026-05-12 03:08:30 +00:00
Some checks failed
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 1m10s
Required
Details
CI / quality (pull_request) Successful in 1m13s
Required
Details
CI / security (pull_request) Successful in 1m13s
Required
Details
CI / typecheck (pull_request) Successful in 1m28s
Required
Details
CI / benchmark-regression (pull_request) Failing after 55s
CI / push-validation (pull_request) Successful in 28s
CI / helm (pull_request) Successful in 38s
CI / build (pull_request) Successful in 43s
Required
Details
CI / integration_tests (pull_request) Successful in 4m18s
Required
Details
CI / e2e_tests (pull_request) Successful in 3m49s
CI / unit_tests (pull_request) Successful in 5m46s
Required
Details
CI / coverage (pull_request) Has been skipped
Required
Details
CI / docker (pull_request) Has been skipped
Required
Details
CI / status-check (pull_request) Failing after 3s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core!11130
No description provided.