Skip to content

fix: clean up orphaned worktrees on bootstrap failure#1

Merged
alexhraber merged 1 commit intodevfrom
fix/worktree-cleanup-on-failure
Feb 22, 2026
Merged

fix: clean up orphaned worktrees on bootstrap failure#1
alexhraber merged 1 commit intodevfrom
fix/worktree-cleanup-on-failure

Conversation

@alexhraber
Copy link
Copy Markdown
Owner

Summary

  • Fix a critical disk fillup vulnerability where failed worktree bootstraps leave orphaned directories, git entries, branches, and database records permanently on disk
  • Add a cleanupFailedWorktree helper that tears down all worktree artifacts when the deferred bootstrap fails at any stage
  • Cover all three failure paths: git reset --hard failure, Instance.provide() failure, and unexpected exceptions in the outer start() promise

Problem

The Worktree.create function has a fire-and-forget bootstrap via setTimeout(() => {...}, 0) at line 357. After git worktree add succeeds and Project.addSandbox() registers the worktree, the function returns info immediately. The actual population (git reset --hard) and bootstrap (Instance.provide()) happen asynchronously.

When either of these steps fails:

  1. The worktree directory remains at ~/.local/share/opencode/worktree/<project-id>/<name>/
  2. The git worktree entry persists in .git/worktrees/
  3. The git branch opencode/<name> remains
  4. The sandbox database record stays in the project

Nothing ever cleans these up. Each retry generates a unique random name (brave-cabin, calm-cactus, etc.), so repeated failures create unbounded orphaned worktrees. For large repos, each can be hundreds of MB.

Changes

Location What changed
worktree/index.ts:359-376 Added cleanupFailedWorktree async helper that removes: directory (with retries), git worktree entry, prunes stale git worktree metadata, deletes the git branch, and removes the sandbox database record
worktree/index.ts:393 git reset --hard failure path now calls cleanupFailedWorktree() before returning
worktree/index.ts:417-419 Instance.provide() failure path now calls cleanupFailedWorktree() before returning
worktree/index.ts:436-438 Top-level start().catch() now calls cleanupFailedWorktree() for any unexpected error
worktree/index.ts:356 Captured Instance.worktree as worktreeCwd before entering setTimeout to ensure stable reference for cleanup git commands

Impact

Before this fix, every failed worktree creation attempt leaked a full repository clone on disk with no way to reclaim the space short of manually finding and deleting orphaned directories. This was the root cause of /tmp and data directory disk fillups reported by users experiencing repeated bootstrap failures (e.g. network issues, disk pressure, permission errors).

Test plan

  • Trigger a worktree creation where git reset --hard fails (e.g., corrupt repo state) and verify the worktree directory, git entry, branch, and sandbox record are all cleaned up
  • Trigger a worktree creation where Instance.provide() throws and verify full cleanup
  • Verify successful worktree creation still works end-to-end (no regression)
  • Verify Worktree.remove() still works correctly for explicitly removed worktrees
  • Run existing test suite: bun test packages/opencode/test/project/worktree-remove.test.ts

When worktree bootstrap fails (either at git reset or Instance.provide),
the worktree directory, git worktree entry, git branch, and database
sandbox record were all left behind permanently. Each failed attempt
creates a new randomly-named worktree, so repeated failures cause
unbounded disk consumption in ~/.local/share/opencode/worktree/.

Add a cleanupFailedWorktree helper that runs on every failure path in
the deferred bootstrap, removing:
- the worktree directory (with retries)
- the git worktree entry (+ prune)
- the git branch
- the project sandbox database record
@alexhraber alexhraber merged commit 0e1c344 into dev Feb 22, 2026
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