Improve worktree ownership first, then reduce repeated work
Proposal for Wout. Read-only assessment of T3 Code at commit 8e6a7323050ea8d2891f745f7e624e84f9fd8d9a. The implementation already has substantial cleanup safeguards. The largest opportunities are inconsistent lifecycle paths, expensive status reads for small UI needs, and an inventory that helps users take action safely.
Recommendation: close the explicit-removal gap first. Then ship a lightweight branch subscription and skip cleanup scans for worktrees whose time-based rules are not due. Consolidate attachment and setup readiness in small steps. Build inspection and selected cleanup on those shared rules.
Evidence is source inspection, including relevant tests. Failure scenarios are plausible interleavings, not reproduced incidents. No speedup has been measured. No repository files, worktrees, running servers, or live databases were changed. PostPlan uses true-black fallback colors because the active client theme could not be read.
Priorities
| Order | Change | Benefit | Relative scope |
|---|---|---|---|
| 1 | Durable, server-owned thread-plus-worktree removal | Fresh ownership checks and completion after client disconnect | Medium; contract, server, web, mobile |
| 2 | Lightweight branch subscription for sidebar rows | Avoid calculating file and branch diffs to read one branch name | Medium; status contract and subscribers |
| 3 | Skip ineligible scans; target final reads; make size accounting optional | Cut unnecessary Git work, repeated global queries, and checkout lease occupancy | Small to medium; server |
| 4 | Consistent attachment, readiness, and recovery | Fewer orphaned checkouts and fewer turns starting before setup | Medium, delivered in separate behavior slices |
| 5 | Read-only inventory, per-checkout keep, selected removal | Understand ownership and reclaim space without enabling broad policy | Medium to large; server and clients |
| 6 | Batch branch-name probes; improve ref and remote refresh paths | Fewer subprocesses and filesystem probes as repositories grow | Small independent improvements after measurement |
1. Make explicit removal use the server's safety rules
Current path: web checks occupants, asks whether to remove the checkout, stops the thread session, deletes the thread, navigates, then separately requests removeWorktree(force: true). The Git workflow delegates directly to the Git driver. Automatic cleanup has fresh occupant/session/terminal checks and a workspace lease; this explicit path does not share that complete protection.
Failure example: a second device starts using the checkout while the confirmation is open. The earlier occupant check cannot authorize removal safely anymore. A disconnect after deleting the thread can also leave the second operation unfinished.
Smallest useful change: include removal intent in durable thread deletion and process it server-side after deletion effects finish. Recheck current users under the same checkout lease used by cleanup and startup. Surface blocked, removed, and failed results to the client. The explicit discard choice can authorize discarding files; it cannot authorize deleting another thread's workspace. Keep the normal automatic preservation rules intact.
Proof: coordinate another occupant arriving, an active terminal, a turn starting, delayed deletion effects, and a response lost after commit using Deferreds. Retry the same command and establish one durable outcome.
Sources: client deletion sequence; direct removal; cleanup rechecks.
2. Stop computing review totals for a branch label
Confirmed: sidebar rows subscribe to local VCS status. In this version, the retained status is read for refName to show a branch mismatch. GitManager.readLocalStatus nevertheless requests branch-change totals. On a cache miss, the lower layer reads porcelain status, working-tree numstat, review merge-base and untracked-file data, and branch numstat.
Proposal: provide a narrow branch subscription, derived from existing branch notifications plus a fresh initial read. Keep complete status for the active Changes view and other consumers that need it. Verify the search-row path and mobile consumers too. If the status broadcaster shares one checkout among subscribers, aggregate demand so a light subscriber does not request detailed work and a detailed subscriber still gets complete data.
Preserve: external terminal branch switches, detached HEAD, deleted paths, reconnect freshness, and detailed counts. Do not reuse a branch-only result as a complete status result. Existing viewport leases and local-only sidebar subscriptions should remain.
Measurement: mount and scroll synthetic 10, 50, and 200-checkout datasets; record actual subscribed paths, subprocess counts by span, server CPU, and status latency. With only lightweight subscribers, detailed diff calls should be absent. Repeat with one detailed subscriber and an external branch switch. These are test dataset sizes, not measured results.
Sources: sidebar subscription; branch-name consumer; full local read; status implementation.
3. Skip work before making the remaining checks cheaper
The simplest win: cleanup checks the Git branch and local changes before calculating age and settlement eligibility. With only a 30-day inactivity rule enabled, a one-day-old idle checkout still gets those Git reads each sweep. Calculate the cheap time-based decision first. If no time-based rule is due and neither merge nor unchanged cleanup can apply, return a not-yet-due reason without inspecting Git. Keep all final destructive checks for eligible candidates.
This changes report meaning: “not yet due” is not proof that files are clean. Full on-demand inspection should remain available separately. Measure Git command counts for many recent worktrees under age-only rules, alongside eligible and merge-rule cases. Do not add sweep-wide concurrency to hide unnecessary work.
Confirmed: each eligible candidate's final check reads all worktree-thread paths, filters them in memory, loads matching shells, and decodes all non-stopped provider sessions. It repeats this before and after size measurement. With C eligible candidates, T worktree references and S session records, the global reference/session portions do work proportional to C × (T + S). This describes query shape, not measured runtime.
For candidates that remain eligible, the candidate holds its checkout lease while measuring bytes. Measurement has a configured 30-second timeout and can visit up to two million entries. That is a cap, not an observed duration. Provider startup uses the same lease.
Proposal: query current thread shells and sessions relevant to the target path, including descendant session working directories. Use a stable normalized key that preserves existing platform and path-alias semantics. Add an index only after examining its query plan. Keep final activity checks fresh. Make size accounting optional or move it before the lease; revalidate removal eligibility after acquiring the lease. A failed estimate already does not need to prevent safe removal.
Proof and measurement: compare rows returned and decoded, SQL calls, filesystem calls, lease occupancy, and competing startup completion on synthetic datasets. Preserve tests for activity arriving during measurement, subagents, archived threads, shared paths, symlinks, and project roots. Any moved measurement becomes an estimate and must be labelled accordingly.
Sources: final checks; measurement; unfiltered reference read.
4. Make attachment and readiness consistent
Attach exactly once
Mobile creates a checkout, then sends a separate thread metadata update. MCP already rechecks thread state, conditionally binds with expectedWorktreePath: null, and rolls back known binding failures. Extract that behavior into a shared server module for mobile and MCP first. Preserve command idempotency and distinguish a known failure from an interrupted response whose binding may have committed. Adopt launch only after its cancellation behavior fits; avoid a repository-wide rewrite.
Start a turn only after required setup
Launch observes setup completion and waits for synchronous scripts. MCP queues its durable continuation before running setup and does not request completion observation. Missing-checkout recreation restores Git files without rerunning project setup in that block. Persist continuation intent early, but gate execution until required setup succeeds. Honor scripts explicitly configured asynchronous. Run setup after actual recreation, rather than on every resume. Retries must not blindly duplicate script side effects.
Distinguish a recovered checkout from a new baseline
Startup accepts an existing path without checking its repository identity in the recreation guard. If the checkout and local/remote branch are all gone, recovery can create the old branch name from the project/default baseline. An existing integration test explicitly expects this fallback, so changing it is a product decision rather than an accidental bug fix.
Validate the checkout's common Git directory and reconcile intentional branch changes. When the original branch is unavailable, explain the lost recovery identity and require a deliberate baseline choice rather than imply the old files were restored. Evaluate checkpoint-backed recovery only after establishing what those refs actually preserve. Keep no-remote and alternate-remote projects supported.
Proof: duplicate and competing attachment commands, dropped responses after commit, synchronous setup held by a Deferred, asynchronous setup, old callbacks after retry, wrong repository at an existing path, and missing local/remote branch. Assert the files and repository identity seen by the provider.
Sources: mobile creation; MCP conditional binding; continuation and setup ordering; recovery fallback; expected fallback behavior.
5. Turn the cleanup report into an actionable inventory
Existing settings already explain why a worktree was kept and report orphan folders. Those are results of the last cleanup sweep, capped at 200 entries. They are not a complete current inventory, and orphan reporting is reached through enabled cleanup policies.
Proposed behavior: inspect checkouts with cleanup disabled; see owning threads, branch, readiness, last relevant activity, and an explicit keep/remove reason; choose individual eligible checkouts for removal. Measure sizes on request with unknown and stale states. Keep this separate from turning on future automatic deletion.
Useful additions are a reversible per-checkout keep control and paginated orphan inspection. Start with project/machine context and the same server inspection rules used by removal. Prefer inspection on demand when cleanup is disabled, so making inventory visible does not add an expensive hourly scan by default. Git registration and thread projections remain authoritative; avoid a second full worktree registry. A manual keep override would be only a small piece of durable policy. External checkouts and ordinary folders need distinct labels; no thread reference alone does not make a directory disposable.
Web and desktop already share settings. Mobile currently exposes creation/submodule/branch-naming settings but lacks equivalent cleanup controls in its worktree page. Any inventory/removal capability should use the same permission-checked server methods for clients and agents. Visual design belongs in a later Paper run after the product scope is chosen.
Sources: orphan reporting; existing report UI; mobile worktree settings.
6. Smaller performance and usability candidates
- Batch branch-name availability reads. Name allocation checks ancestors and tries up to 100 suffixes with separate Git commands. Read the relevant local ref names once and choose in memory. Preserve ancestor/descendant ref conflicts and handle a concurrent creator winning the final atomic Git operation. Benchmark command counts with occupied suffixes.
- Avoid probing every checkout on each ref refresh. Ref snapshots currently stat registered paths, with concurrency 16. This is linear in registered checkouts per cache miss. Preserve the distinction between a Git registration and a usable path; do not simply trust stale registrations. Consider lazy validation only if its callers handle missing paths correctly. Measure first.
- Revisit remote cache ownership. The broadcaster invalidates the remote result cache and checks branch before and after reading. Some option combinations bypass that cache already. A one-second cache versus a much longer poll interval also means merely removing invalidation may produce little gain. Any redesign needs branch/HEAD/upstream freshness and fetch/config invalidation, plus protection against branch switches during a read.
- Reuse known upstream metadata within a refresh. Shared fetch is already coalesced by common Git directory and remote, but callers probe branch/upstream before reaching that cache. Reuse facts from the same read where valid; do not fetch the wrong remote after an external switch.
- Prevent generated path collisions. Short branch names replace slashes with hyphens, so
feat/aandfeat-aselect the same folder. Repository basenames also share a parent namespace. Git refuses the conflicting checkout; this is not observed data loss. Consider a stable identity suffix or a clear alternate-path retry while preserving existing checkout locations. - Correct handoff guidance. The MCP tool promises that the worktree survives thread deletion, but cleanup-on-delete can remove it. Describe that condition accurately. Any implementation must run the model-facing evals required by
evals/README.md.
Sources: name allocation; ref snapshots; remote freshness guard; cache bypass options.
Keep the working parts
Automatic cleanup already protects active sessions and terminals, shared thread families, pending deletion effects, late file edits, submodule history, managed paths, and project roots. Missing-checkout recreation, previous directory support, shared fetch/backoff, viewport subscription leases, local refresh single-flight, repository-path caches, and parallel Git checkout already exist. Preserve these rather than rebuilding them.
Avoid writable shared node_modules, one filesystem watcher per worktree, unconditional higher checkout parallelism, automatic deletion of every orphan, a second worktree registry, and broad cleanup concurrency as starting points. Each either introduces ownership risk or optimizes work the code already coordinates.
Council synthesis
One independent assessment each from the requested three members, all at high effort. The lead verified the main paths against the source and nearby tests. This was an exploratory architecture assessment, so scores were not requested or invented.
| Member | Strongest contribution | How it shaped this proposal |
|---|---|---|
| GPT 6.1 Sol | Server ownership, conditional binding, readiness, identity-aware recovery, repeated final-check reads | Use a small shared lifecycle module; retain existing orchestration and Git owners |
| Claude Opus 5.5 | Forced explicit deletion, setup omitted on recreation, disabled-by-default inventory, early eligibility, mobile parity | Promote eligibility filtering; make inventory independent of cleanup policy; include setup after recreation |
| Grok 4.7 | Status overfetching, cache behavior, repeated ref stats, branch allocation probes | Promote the narrow branch subscription and batch probes; investigate cache changes conservatively |
Judgment calls and disagreements
- A non-force delete is useful but incomplete. It protects files but does not establish fresh thread/session ownership or survive client disconnect. It can be an interim safeguard; the durable server operation is the complete direction.
- Inventory should work with all policies off. Opus suggested running the orphan scan independently of policy. Start with on-demand inspection rather than unconditionally adding hourly scans.
- Do not remove branch race checks to improve cache hits. Grok identified real extra calls, but a poll longer than the cache TTL may get little reuse anyway. Preserve the before/after validation and measure the actual saving.
- Do not blindly trust registered paths. Lazy path validation may help large repositories, but missing directories currently affect which worktree can be reused. Establish caller behavior before changing that contract.
- Recovery fallback is intentional today. Sol flagged its risk; the lead found an integration test expecting it. A safer alternative requires an explicit product decision.
Across surfaces: web and desktop share the client path; mobile needs the same server operations and cleanup visibility. Agents and scheduled launches should call service methods. Contracts carry permissions and typed results. Keep local, remote, relay, and tunnel operations on the destination environment. Cover retries, cancellation, disconnect after commit, external Git changes, shared child threads, and archived owners. Provider-specific startup behavior stays in adapters; these ownership rules should not vary by provider.
Decision before implementation
Recommended first slice: server-owned explicit removal, the narrow sidebar branch subscription, and early cleanup eligibility, each measured and tested with its own behavior. Then attachment/setup parity. Keep inventory and selected cleanup as a follow-up product decision. This proposal does not publish tickets or authorize implementation.