feat(worktrees): manage lifecycle on orchestrator v2 - #5589
StiensWout wants to merge 8 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
There was a problem hiding this comment.
Reviewed the new worktree services against the Effect service conventions. Four convention issues found: two standalone *Shape service interfaces, a redundant singleton operation discriminator plus free-form message on the new worktree error classes, and a hidden optional service dependency in ProviderTurnStartService.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Effect service conventions review of the worktree management services. Prior findings on WorktreeLifecycle/WorktreeRevivalService shape interfaces, the unstructured worktree error payloads, and the Effect.serviceOption acquisition of WorktreeRevivalService all look addressed. A few smaller convention issues remain.
Posted via Macroscope — Effect Service Conventions
25de21d to
0af2a6e
Compare
0f4d58b to
8f7ca24
Compare
22bd872 to
a27c1cc
Compare
61184a3 to
d56b638
Compare
There was a problem hiding this comment.
One finding: raw git stderr is copied into a new error attribute. Everything flagged in earlier runs (service-shape interfaces, make/layer naming, the single-use mutationError helper, the parseWorktreeBranchPaths shim, structural stages on the new worktree errors, and the hidden WorktreeRevivalService requirement) is resolved in this revision.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Reviewed the Effect service conventions in this update. Previously flagged items (inline service interfaces, plain make/layer names, structural error stages with derived messages, required WorktreeRevivalService acquisition in ProviderTurnStartService, shared worktree porcelain parser, bounded git worktree list error context) all look resolved. One remaining error-modeling nit below.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces a large, user-facing worktree lifecycle feature that changes provider startup and cleanup behavior, performs Git/filesystem mutations, and adds authenticated RPCs and default capability exposure. Its cross-cutting runtime impact and auth-package change require human review. You can add or adjust custom eligibility rules. Learn more. |
There was a problem hiding this comment.
One finding on the new GitVcsDriver.listWorkspaces truncation error: its context fields are hardcoded/fabricated rather than derived from the actual command and output.
Posted via Macroscope — Effect Service Conventions
a186d64 to
5b1a115
Compare
f63b335 to
08a1b86
Compare
01ce108 to
67217c7
Compare
7e67512 to
48b7f3f
Compare
Rebased onto the current t3code/codex-turn-mapping base. The base's interim inline worktree repair in ProviderTurnStartService is replaced by the revival service this change introduces. Revival only creates a missing directory. An existing worktree is left as is, whatever ref it has checked out and wherever it lives. Only per-thread provider sessions restart after a revival; a session shared across threads stays open. Turn starts skip the mutation permit when the directory exists, and the reaper holds it per worktree instead of per batch. Turn start no longer serializes on a per-session lock. Restarts only apply to per-thread sessions, whose ids are allocated per thread, so the lock protected nothing while serializing every Codex start behind handoff delivery and baseline capture. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A recreated worktree started the provider turn as soon as its setup script was written to the terminal, even when the script is marked to finish first. Revival now observes the script's exit: a required script is awaited outside the mutation permit, concurrent turn starts share one run per worktree generation, and the run lives in the service scope so a cancelled turn start cannot strand the next one. A failed revival settles the run with the reason instead of starting the provider in a missing or half set up worktree. The reaper read its policy once per sweep, so turning cleanup off or lengthening retention while it ran still removed worktrees. Prune now takes a recheck that runs under the permit right before each removal, and the reaper passes its policy through it. A registration whose directory was deleted stayed in the inventory with an unreadable status that nothing could ever remove. It is left out now; Git's own gc, the next removal, and revival prune it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The memory fixture builds the turn-start layer by hand in an untyped module, so it still supplied the base's interim repair dependencies and not the revival service the ported turn start now requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Worktrees search result inherited Source Control's environment-defaults scope, so choosing it with a project selected showed an unavailable notice, although the section lists every connected environment whatever the selection. A search item can now opt out of its page's scope with null. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The base's orchestration services now read projects from a store that the runtime provides after them. Worktree management was merged in below that store, so the server layer no longer typechecked; it now sits right after the orchestration services it depends on. The project settings upgrade test provides the no-op revival service in place of the removed inline repair. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings > Storage (pingdotgg#11598) now removes inactive worktrees and worktrees whose last thread was deleted, per machine and per project, off by default. This branch still ran its own reaper and deletion cleanup with a second retention setting that defaulted to 14 days, and it hid the desktop "delete the worktree too?" prompt that Storage cleanup keeps when its deletion rule is off. Drop the reaper, the deletion cleanup, the worktrees retention settings and their rows, the orphaned flag only they read, and the prompt change. The inventory, manual removal, and revival before a turn stay. Storage cleanup now removes under the same mutation permit and bumps the inventory revision, so its removals serialize with revival and show up in the Worktrees list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Thread launches, PR threads, and the MCP handoff created worktrees under the server-wide mutation permit, so parallel launches (one worktree per model) ran their checkout and submodule init one after another, and a slow checkout held up removals and turn starts that needed a revival. Creating a worktree only adds a new path, and git locks it against prune while it initializes, so creation now just bumps the inventory revision. Removals and revival still hold the permit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The inventory list and its change stream are separate requests, and the Worktrees section took the stream's first revision as already read. A change landing between the list read and the subscription, or while the section was closed and the list still cached, stayed hidden until another change or a manual refresh. The list now reports the revision it was read at, and the section refetches once whenever the stream shows a different one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Note This comment is posted by Julius' dot Closing for incomplete UI verification. Both attached images show the added list in light and dark mode, including cleanup controls that have since been removed. Neither shows the before state or the Remove interaction. Please attach current before/after views and a short recording of confirming a safe removal and seeing its row disappear, then request reconsideration. |
Important
Stacked pull request. This targets
t3code/codex-turn-mappingfrom #2829. After #2829 merges, rebase this branch and retarget the PR tomain.Problem
Thread worktrees pile up and there is no way to see them or remove one from the app. Settings → Storage (#11598) can remove them automatically, but only by rule, and you can't see what it kept or why. The base also recreates a missing worktree before a turn, but it skips the project's setup script, and when recreation fails it starts the provider in a directory that doesn't exist.
Solution
git statusper worktree, concurrently with the ahead-of-default count. On a 2-vCPU machine with 8 worktrees it went from 5.0 s to 0.6 s.Automatic cleanup is not part of this PR anymore. Earlier revisions had their own reaper, a "remove with last thread" option, and a 14-day retention setting. Storage cleanup covers all of that now, off by default and per project, so I dropped them along with the change that hid the desktop "delete the worktree too?" prompt.
Other details:
git worktree list --porcelain -zis parsed everywhere, projects outside a Git repository are skipped instead of failing the inventory, registrations whose directory is gone are left out, paths are canonicalized, and revival rejects symlinked ancestors that escape the managed worktree root.Screenshots
These predate the removal of the two cleanup policy rows above the list. The list itself is unchanged.
Verification
Initial implementation and the first refresh were produced with GPT-5.6 Sol via Codex in T3 Code. The one-line settings layout, detached-HEAD handling, inventory speed-up, and review follow-ups were done by Claude Fable 5 via Claude Code in T3 Code. The rebases through 2026-09-16 were done by Claude Fable 5.1 via Claude Code in T3 Code. The rebases on 2026-09-26 and 2026-09-30, the fixes for Macroscope's review, and dropping automatic cleanup in favor of Storage cleanup were done by Claude Opus 5.5 via Claude Code in T3 Code.
🤖 Generated with Claude Code
Note
Add server-side worktree lifecycle management to orchestrator v2
WorktreeService,WorktreeRevivalService,WorktreeLifecycle,WorktreeReaper, andWorktreeDeletionCleanupservices that handle listing, pruning, reviving, and reaping worktrees on the serverProviderTurnStartServicenow revives the thread's worktree before starting a provider turn, serializes session starts perProviderSessionId, and closes/reopens the provider session when the worktree was revived or its generation/path changedvcs.listWorktrees,vcs.subscribeWorktreeInventory,vcs.pruneWorktrees) with auth scopes, and aworktreeManagementcapability flag so clients gate features on server supportautoPruneAfterDaysdefault 14,deleteOrphanedImmediatelydefault false), and pruning;useThreadActionsskips legacy client-side orphan cleanup when the capability is presentGitWorkflowServicemethods (preparePullRequestThread,createWorktree,removeWorktree) now run under aWorktreeLifecyclemutation permit and signal inventory changesProviderTurnStartServiceno longer emitsprovider-session.updatedandprovider-thread.updatedevents during the initial running transition; consumers relying on those events in that phase will need to userun.updated,run-attempt.updated, ornode.updatedinstead📊 Macroscope summarized 67217c7. 34 files reviewed, 8 issues evaluated, 5 issues filtered, 3 comments posted
🗂️ Filtered Issues
apps/server/src/git/GitWorkflowService.ts — 0 comments posted, 1 evaluated, 1 filtered
removeWorktreenow tries to acquireWorktreeLifecycle's non-reentrant single-permit semaphore even when invoked byWorktreeService.removeIfStillSafe, which already holds that permit. The nested acquisition waits forever, so every automatic/manual safe cleanup path that reaches this call hangs instead of removing the worktree. [ Out of scope (triage) ]apps/server/src/vcs/WorktreeLifecycle.ts — 0 comments posted, 1 evaluated, 1 filtered
changesemits the currentSubscriptionRefrevision as a subscriber's first item, but the inventory RPC has no matching revision/snapshot handshake. A mutation can complete after a client startsvcsListWorktreesand before it subscribes: the list response is then stale while the first stream item is the already-incremented revision. The client treats that first revision as its baseline and does not refresh, so the settings inventory remains stale until another mutation or manual refresh. [ Previously rejected ]apps/server/src/vcs/WorktreeReaper.ts — 1 comment posted, 2 evaluated, 1 filtered
pruneWorktreescan remove a worktree after its last thread has become active.pruneWorktrees's final thread snapshot is not synchronized with thread lifecycle updates (its implementation explicitly notes links/status can change without the mutation permit), so anunsettleor new turn can land after that snapshot but before Git removal. The reaper makes this race recurring; the active thread is left pointing at a removed working directory until a later revival path repairs it. [ Previously rejected ]apps/server/src/vcs/WorktreeService.ts — 1 comment posted, 2 evaluated, 1 filtered
removeWorktreeruns. In that interleaving the new active thread is absent fromreferences, while the clean worktree is removed successfully, leaving its newly linked thread with a missing working directory. [ Previously rejected ]apps/web/src/components/settings/SourceControlSettings.tsx — 0 comments posted, 1 evaluated, 1 filtered
listhas already read its Git worktree list but before the subscription is established, the subscription's initial value contains the new revision and is discarded here; the older list response is then rendered and no later refresh occurs. The Worktrees settings can therefore keep showing a removed or newly-created worktree until the user manually refreshes. [ Already posted ]Note
High Risk
Touches provider turn startup, shared session close/reopen, and automatic worktree deletion; race-sensitive paths are tested but mistakes could strand runs or remove worktrees incorrectly.
Overview
Adds server-owned Git worktree lifecycle for orchestration v2: inventory from
git worktree list, safe pruning rules, automatic retention/orphan cleanup, and revival of missing thread worktrees before provider turns run.Git & workflow: New
listWorkspaces/ shared porcelain parsing (GitWorktree.ts), with output-size limits.GitWorkflowServiceroutes create/remove/prepare-PR-thread work throughWorktreeLifecycle(serialized mutations + inventory revision stream).Orchestration:
ProviderTurnStartServicecallsWorktreeRevivalService.reviveForThread, then serializes startup perProviderSessionIdviaKeyedSerialExecutor. If a worktree was revived or its generation/path changed, it closes and reopens the shared provider session so cwd stays correct, with careful handling when runs are superseded mid-restart.Background jobs:
WorktreeDeletionCleanupreacts tothread.deletedevents;WorktreeReaperperiodically prunes inactive safe worktrees per settings. Both delegate toWorktreeServicefor last-moment safety checks.Product surface: Enables
worktreeManagementserver capability and RPC auth forvcsListWorktrees,subscribeWorktreeInventory, andvcsPruneWorktrees. Layers wired inserver.ts/ startup after the effect worker starts.Reviewed by Cursor Bugbot for commit bdc613b9f3f9fecc9d611b94a2fa014b393a27ab. Bugbot is set up for automated code reviews on this repo. Configure here.