Skip to content

feat(server): remove worktrees when their last thread settles - #3

Merged
Fryuni merged 1 commit into
mainfrom
feature/settled-worktree-cleanup
Sep 12, 2026
Merged

Fryuni merged 1 commit into
mainfrom
feature/settled-worktree-cleanup

Conversation

@Fryuni

@Fryuni Fryuni commented Sep 12, 2026

Copy link
Copy Markdown
Owner

What Changed

Settling a thread now removes its worktree once nothing active still uses it. The settlement reactor listens for thread.settled and stopped thread.session-set events and enqueues cleanup, which:

  • canonicalizes paths (cached realPath) before comparing, so worktrees shared through symlink aliases are recognized and kept
  • never removes the project's main workspace root
  • waits for every other thread on the worktree to settle and stop its session (archived and already-settled threads don't block)
  • leaves the branch in place — git refuses dirty worktrees, a refused removal is logged, and cleanup continues
  • skips stale settlement events for threads that were reactivated

The thread keeps its saved branch, so starting a new turn recreates the worktree. Covered by reactor tests for each guard rail, an integration check that the branch survives removal, and a note in docs/user/thread-sidebar.md.

Why

Settled threads left their worktrees on disk forever, so long-lived environments accumulated stale directories users had to remove by hand. Worktrees are cheap to recreate from the saved branch, so reclaiming them once the last thread settles loses nothing. Doing it inside the existing settlement reactor keeps cleanup event-driven and drain-ordered with the rest of settlement rather than adding another sweeper.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Worked by GLM 5.3 Flash via opencode.

- removes a settled thread's worktree once no active threads share it and its session stops
- preserves the project root, branches, and dirty worktrees
- recreates removed worktrees from the saved branch on the next turn
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-12T19:12:57.986267Z 8f36cd8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f36cd8707

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +77 to +78
const worktreePath = yield* canonicalize(thread.worktreePath);
if (worktreePath === (yield* canonicalize(project.workspaceRoot))) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Protect every configured project root

When a linked checkout is also registered as another project's workspaceRoot, and that project has no non-archived thread currently using it, this guard compares the target only with the settling thread's project root. The later loop derives roots only from other threads, so git worktree remove can delete the other configured project's root and leave that project unusable. Compare the target against every entry in snapshot.projects before removing it.

Useful? React with 👍 / 👎.

Comment on lines +92 to +93
// Git preserves the branch and refuses to remove a main or dirty worktree.
yield* gitWorkflow.removeWorktree({ cwd: project.workspaceRoot, path: worktreePath });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Serialize cleanup with resumed turn startup

When a user submits a new turn immediately after settlement, cleanup can pass its snapshot checks before the turn's thread.unsettled event is projected. Because this removal is not serialized with ProviderCommandReactor.ensureThreadWorktree, turn startup can observe the directory before deletion and skip recreation, after which this call deletes the cwd underneath the newly starting provider. Recheck and remove under a per-worktree lock shared with turn startup, or otherwise serialize these operations.

Useful? React with 👍 / 👎.

Comment on lines +320 to +325
const events = yield* engine.subscribeDomainEvents;
yield* forkParked(
Stream.runForEach(events, (event) =>
event.type === "thread.settled" ||
(event.type === "thread.session-set" && event.payload.session.status === "stopped")
? cleanupWorker.enqueue(event.payload.threadId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reconcile missed settlements when the reactor starts

If the server exits after a settlement is persisted but before cleanup completes, this hot domain-event subscription will not replay that event after restart. Startup never scans the snapshot for already-settled threads, and the periodic settlement sweep excludes them, so the worktree remains indefinitely. Enqueue eligible settled threads during startup or persist and retry a cleanup receipt.

AGENTS.md reference: AGENTS.md:L141-L143

Useful? React with 👍 / 👎.

@Fryuni
Fryuni merged commit d384ce5 into main Sep 12, 2026
@Fryuni
Fryuni deleted the feature/settled-worktree-cleanup branch September 13, 2026 14:43
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