Skip to content

fix(git): clean up the worktree+branch on any failure after worktree add (P6-GIT-05)#41

Merged
Reederey87 merged 3 commits into
mainfrom
fix/p6-git-05
Jul 3, 2026
Merged

fix(git): clean up the worktree+branch on any failure after worktree add (P6-GIT-05)#41
Reederey87 merged 3 commits into
mainfrom
fix/p6-git-05

Conversation

@Reederey87

@Reederey87 Reederey87 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Pass 6 P2 quick-win wave, PR 4 of 4 (P6-GIT-05, docs/audits/AUDIT_RECOMMENDATIONS_2026-07-01_PASS6.md).

createFreshWorktree leaked a live, DB-invisible worktree + agent/... branch whenever a step failed after git worktree add: the LFS pull (lfs_policy=agent|always on a flaky network), the CurrentDevice lookup, or the InsertWorktree DB write. Because nothing was recorded in SQLite, worktree list/cleanup could never see or reap the orphan, and every agent run/worktree new retry minted a fresh random-suffixed branch — compounding the leak.

Fix:

  • A cleanupOrphan closure (WorktreeRemove + branch -D) runs on each of the three post-add failure returns.
  • The agent run file-policy-denial cleanup (M2) now deletes the branch too, not just the worktree.
  • The LFS failure error now names the worktree path.
  • The doctor orphan-worktree check is deliberately out of scope (tracked in the audit's recommendation as a follow-up).

Tests

  • TestCreateFreshWorktreeCleansUpAfterLFSPullFailure — a PATH-shim fake git forces git lfs pull to exit 42; asserts the error contains the worktree path, the checkout is gone, and no agent/* branch remains.
  • TestCreateFreshWorktreeCleansUpAfterInsertWorktreeFailure — a SQLite BEFORE INSERT trigger forces the DB insert to fail; same post-conditions.

Validation

  • gofmt -w cmd internal
  • golangci-lint run (0 issues)
  • go run ./cmd/spec-drift --base origin/main --head HEAD (pass)
  • GOCACHE=/tmp/devstrap-gocache go test -race ./... (green)

Specs: 08 + 10 (cleanup guarantee documented), 16 (test inventory + e2e scenario steps), 18 work log; ledger reconciled (open-count line harmonized with sibling wave PRs at merge time).

Implementation delegated to gpt-5.5 (Codex) per the model policy; diff reviewed line-by-line.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fresh worktree creation now cleans up properly on failures, removing the partial checkout and deleting the associated branch so no stray worktrees remain.
    • Agent runs that fail policy checks now also remove the newly created branch.
    • LFS-related failures now include the worktree path for clearer troubleshooting.
  • Tests
    • Added regression tests covering cleanup after LFS pull failures and after worktree insert failures, verifying no leftover agent/* branches or worktree directories.
  • Documentation
    • Updated specs and test plan notes to reflect the new failure-time cleanup behavior and error details.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Reederey87, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 308a5f76-a7ca-4569-b01a-dcb7955dfa1c

📥 Commits

Reviewing files that changed from the base of the PR and between 78cacd6 and c9abf31.

📒 Files selected for processing (8)
  • docs/audits/README.md
  • internal/cli/agent.go
  • internal/cli/worktree.go
  • internal/cli/worktree_test.go
  • spec/08_GIT_MATERIALIZATION_AND_WORKTREES.md
  • spec/10_AGENT_WORKSPACES_AND_POLICIES.md
  • spec/16_TEST_PLAN.md
  • spec/18_WORK_LOG.md
📝 Walkthrough

Walkthrough

Adds cleanup for freshly created worktrees and branches when post-add steps fail, extends CLI regression coverage, and updates related specs, work log, and audit ledger entries. The agent run failure path also now deletes the created branch after worktree cleanup.

Changes

Fresh worktree cleanup implementation

Layer / File(s) Summary
Cleanup logic in worktree creation and agent run
internal/cli/worktree.go, internal/cli/agent.go
Adds cleanup of newly created worktrees and generated branches when post-add failures occur during LFS policy handling, current-device lookup, or SQLite worktree insertion, updates LFS failure messaging to include the worktree path, and force-deletes the branch in the agent run failure path.
Regression tests for cleanup paths
internal/cli/worktree_test.go
Adds CLI tests for LFS-pull failure and SQLite insert-failure cleanup, plus helpers for repo setup, fake git LFS failure injection, SQLite trigger injection, stderr path parsing, and orphan worktree or branch assertions.
Spec and ledger updates
spec/08_GIT_MATERIALIZATION_AND_WORKTREES.md, spec/10_AGENT_WORKSPACES_AND_POLICIES.md, spec/16_TEST_PLAN.md, spec/18_WORK_LOG.md, docs/audits/README.md
Updates worktree, agent workspace, and test plan specs to describe the cleanup behavior, adds a work-log entry for the change, and moves the audit finding from open to shipped with the backlog count updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: cleaning up orphan worktrees and branches after post-add failures.
Description check ✅ Passed The description covers Summary, Tests, Validation, and spec updates, and is mostly aligned with the template despite not using the exact safety checklist format.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/p6-git-05

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
internal/cli/worktree_test.go (1)

76-83: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant/tautological assertion.

wtPath is extracted from stderr by lfsFailureWorktreePath, so strings.Contains(stderr, wtPath) is true by construction and never catches a real regression. Either drop it or replace with a check that the message doesn't imply the worktree survives (once the message wording issue on worktree.go line 241 is addressed).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/worktree_test.go` around lines 76 - 83, The assertion in
worktree_test.go is tautological because lfsFailureWorktreePath extracts wtPath
from stderr, so checking strings.Contains(stderr, wtPath) adds no value. Update
the test around executeForTest and lfsFailureWorktreePath to either remove that
check or replace it with a meaningful assertion about the LFS failure message
from worktree new, ensuring the test verifies the intended wording/behavior
instead of reusing a value parsed from the same output.
internal/cli/worktree.go (1)

174-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate worktree/branch cleanup logic vs. internal/cli/agent.go.

The same "WorktreeRemove then force-delete branch" sequence is duplicated between this closure and the inline cleanup in newAgentRunCommand's RunE (internal/cli/agent.go, Lines 79-80). Extracting a shared helper (e.g., cleanupOrphanWorktree(ctx, r, repoPath, wtPath, branch, stdout)) would fix both call sites at once for the error-swallowing/context-reuse concerns raised above, and prevent the two copies from drifting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/worktree.go` around lines 174 - 188, The orphan worktree cleanup
logic is duplicated here and in newAgentRunCommand’s RunE, so refactor the
repeated WorktreeRemove plus force-delete branch sequence into a shared helper
such as cleanupOrphanWorktree that takes the context, runner, repo path,
worktree path, branch, and stdout; update this closure in
internal/cli/worktree.go and the inline cleanup in internal/cli/agent.go to call
that helper so both sites stay consistent and share the same error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/agent.go`:
- Around line 79-81: The cleanup in the worktree removal path is still silently
discarding errors from gitRunner(opts).WorktreeRemove and gitRunner(opts).Run
for branch deletion, and it reuses cmd.Context() even when that context may
already be canceled. Update this flow to capture and log or return cleanup
errors instead of ignoring them, and invoke the git cleanup using a fresh
background/derived context rather than cmd.Context(). If possible, extract the
cleanup logic into the same shared helper used by createFreshWorktree so both
call sites handle WorktreeRemove, branch -D, and store.MarkWorktreeRemoved
consistently.

In `@internal/cli/worktree.go`:
- Line 241: The error returned from createFreshWorktree is now misleading
because cleanupOrphan removes the worktree after an LFS pull failure, so the
message should no longer imply the worktree remains or that pointer-file cleanup
is needed. Update the fmt.Errorf text in createFreshWorktree to reflect that the
worktree was cleaned up after the LFS failure, and keep it consistent with any
test parsing in
lfsFailureWorktreePath/TestCreateFreshWorktreeCleansUpAfterLFSPullFailure by
adjusting the fixed markers there at the same time.
- Around line 174-188: The cleanup in worktree creation is swallowing failures
from both WorktreeRemove and branch deletion, so orphaned checkouts can remain
without any visibility. Update cleanupOrphan in internal/cli/worktree.go to
capture and surface errors from r.WorktreeRemove and r.Run("branch", "-D",
branch), using stdout to emit a warning if either cleanup step fails. Keep the
existing call sites in the applyWorktreeLFSPolicy and store.CurrentDevice error
paths, but make sure cleanupOrphan reports any manual cleanup needed instead of
discarding the result.
- Around line 174-188: The orphan cleanup in worktree setup can be cancelled
along with the main request, so cleanup may never run to completion and leave
the checkout/branch behind. Update the cleanupOrphan logic in worktree.go to use
a detached background context, ideally with a short timeout, for both
r.WorktreeRemove and r.Run so cleanup can proceed even after the original ctx is
interrupted. Keep the rest of the error handling in the worktree creation flow
the same.

---

Nitpick comments:
In `@internal/cli/worktree_test.go`:
- Around line 76-83: The assertion in worktree_test.go is tautological because
lfsFailureWorktreePath extracts wtPath from stderr, so checking
strings.Contains(stderr, wtPath) adds no value. Update the test around
executeForTest and lfsFailureWorktreePath to either remove that check or replace
it with a meaningful assertion about the LFS failure message from worktree new,
ensuring the test verifies the intended wording/behavior instead of reusing a
value parsed from the same output.

In `@internal/cli/worktree.go`:
- Around line 174-188: The orphan worktree cleanup logic is duplicated here and
in newAgentRunCommand’s RunE, so refactor the repeated WorktreeRemove plus
force-delete branch sequence into a shared helper such as cleanupOrphanWorktree
that takes the context, runner, repo path, worktree path, branch, and stdout;
update this closure in internal/cli/worktree.go and the inline cleanup in
internal/cli/agent.go to call that helper so both sites stay consistent and
share the same error handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4719e4e5-e478-4957-bc79-24754f79d2f6

📥 Commits

Reviewing files that changed from the base of the PR and between e00a21b and 6823819.

📒 Files selected for processing (8)
  • docs/audits/README.md
  • internal/cli/agent.go
  • internal/cli/worktree.go
  • internal/cli/worktree_test.go
  • spec/08_GIT_MATERIALIZATION_AND_WORKTREES.md
  • spec/10_AGENT_WORKSPACES_AND_POLICIES.md
  • spec/16_TEST_PLAN.md
  • spec/18_WORK_LOG.md

Comment thread internal/cli/agent.go Outdated
Comment thread internal/cli/worktree.go
Comment thread internal/cli/worktree.go
Reederey87 and others added 2 commits July 3, 2026 04:37
…add (P6-GIT-05)

After a successful `git worktree add`, three failure returns in
createFreshWorktree (LFS pull, CurrentDevice, InsertWorktree) leaked a
live checkout under ~/.devstrap/worktrees/ plus an agent/... branch that
SQLite never saw — invisible to `worktree list`/`cleanup` and to the
planned DB-driven GC, compounding on every retry. A cleanupOrphan
closure now removes both on each of those returns; the agent-run
file-policy-denial cleanup deletes the branch too, and the LFS error
names the worktree path.

Pinned by TestCreateFreshWorktreeCleansUpAfterLFSPullFailure (fake-git
PATH shim forces `git lfs pull` to fail) and
TestCreateFreshWorktreeCleansUpAfterInsertWorktreeFailure (SQLite
BEFORE INSERT trigger forces the DB insert to fail): in both, the
checkout directory is gone and `git branch --list 'agent/*'` is empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntext (review)

Dual-review hardening (opus-4.8, Minor): cleanupOrphan reused the failing
ctx, so when the post-add failure was itself a cancellation (Ctrl-C
mid-LFS-pull), both cleanup git commands no-op'd on the already-cancelled
ctx and the orphan leaked anyway — the exact leak the fix targets. The
cleanup now runs under context.WithoutCancel with a 2-minute cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Reederey87 Reederey87 enabled auto-merge (squash) July 3, 2026 08:38
…th sites (CodeRabbit)

Both cleanup call sites (createFreshWorktree failure paths and the
agent-run policy-denial M2 path) now go through removeOrphanWorktree:
detached bounded context (WithoutCancel + 2m — already applied to the
first site, now to both) and WorktreeRemove/branch -D/MarkWorktreeRemoved
failures surfaced as warnings instead of silently swallowed, so an
operator knows when manual cleanup is needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Reederey87 Reederey87 merged commit 30146c2 into main Jul 3, 2026
11 checks passed
@Reederey87 Reederey87 deleted the fix/p6-git-05 branch July 3, 2026 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant