WIP: Better defaults for worktree vs local checkout - #3957
Conversation
…ee registry
Implements the new-thread workspace model: every new thread starts from the
resolved default workspace mode instead of inheriting the previous thread's
choices, and worktree threads pin a fresh remote base with visible provenance.
Surface-derived defaults
- New `deriveThreadEnvModeFromSurface` setting (default on): attached
surfaces (desktop app, loopback browser on a host-managed environment)
default to the current checkout; detached surfaces (remote browser,
mobile, relay/SSH) default to a fresh worktree. Explicit
`defaultThreadEnvMode` still pins a mode globally when derivation is off,
keeping the wire value decodable by older clients.
- Sticky per-draft inheritance retired: sidebar/new-thread actions no longer
seed mode/branch/worktree from the active thread. `chat.newLocal` remains
the explicit current-checkout exception (draft-only).
- Per-project override (`projectThreadEnvModeOverrides` client setting) via
an explicit "Always … for this project" action in the workspace picker —
the only sanctioned way a composer choice retrains the default.
Fresh worktree bases without a blocking fetch
- `WorktreeBasePlanner`: fetch within the 30s freshness window counts as
fresh; concurrent fetches dedupe per repository; a failed fetch with a
successful fetch within the last hour proceeds on the last-known remote
commit (fail-visible, "stale" provenance); otherwise the bootstrap fails
closed and never silently falls back to a local ref.
- Composer prefetch: worktree-mode drafts fire `vcs.prefetchRemote` on open
so the send-time check almost always hits the fresh window.
- `newWorktreesStartFromOrigin` now defaults on.
Provenance and registry
- Bootstrap records a `worktree.base-pinned` thread activity (base ref, sha,
provenance, fetch time); the branch toolbar renders the pinned fact
("Worktree · a1b2c3f") with provenance on hover.
- `WorktreeRegistry` persists thread↔worktree records to
`stateDir/worktree-registry.json`, updated at bootstrap and on thread
deletion — groundwork for lifecycle/GC tooling.
- Deterministic branch naming: worktree branches derive from the thread id
(`t3code/<uuid-prefix>`), with collision suffixing server-side.
Composer UX
- Dirty-checkout disclosure both directions with one-click mode switch.
- Workspace picker labels choices as "this draft only".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
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 Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
| ? { startFromOrigin: seedContext.startFromOrigin } | ||
| : {}), | ||
| }), | ||
| handleNewThread(scopeProjectRef(member.environmentId, member.id)), |
There was a problem hiding this comment.
🟡 Medium components/Sidebar.tsx:1838
When creating a new thread from the new-thread menu for a grouped project, the selected member's surface default is not passed to handleNewThread, so the draft can default to the wrong env mode. If useNewThreadHandler reuses the logical project group's existing draft (from a different member/environment), setLogicalProjectDraftThreadId detects a project change and createDraftThreadState resets the draft to "local" instead of deriving the selected member's surface default. Picking a detached/remote member can therefore create a current-checkout draft when it should default to a worktree. Pass the selected member's resolved default env mode into handleNewThread, or re-resolve defaults when a reused draft changes project.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/Sidebar.tsx around line 1838:
When creating a new thread from the new-thread menu for a grouped project, the selected member's surface default is not passed to `handleNewThread`, so the draft can default to the wrong env mode. If `useNewThreadHandler` reuses the logical project group's existing draft (from a different member/environment), `setLogicalProjectDraftThreadId` detects a project change and `createDraftThreadState` resets the draft to `"local"` instead of deriving the selected member's surface default. Picking a detached/remote member can therefore create a current-checkout draft when it should default to a worktree. Pass the selected member's resolved default env mode into `handleNewThread`, or re-resolve defaults when a reused draft changes project.
There was a problem hiding this comment.
Fixed in 233e0b4: useNewThreadHandler now detects when a stored draft is being reused for a different project member and re-derives the env mode (and startFromOrigin) from that member's surface default instead of relying on the store's hard-coded local reset.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces significant new features including surface-derived workspace defaults, a worktree registry, and pinned worktree bases with freshness tracking. The scope includes new services, new RPC endpoints, settings changes, and UI modifications. An unresolved review comment also identifies a potential bug in thread creation for grouped projects. You can customize Macroscope's approvability policy. Learn more. |
- Share a single WorktreeRegistry instance between the deletion reactor and the ws layer (previously two instances with divergent in-memory state). - WorktreeRegistry: take the semaphore for reads too so a first-access disk load can't clobber a concurrent mutation, and only update the in-memory registry after the persist succeeds. - WorktreeBasePlanner: claim the in-flight fetch slot synchronously so concurrent pin/prefetch calls can't start duplicate fetches. - Recognize uniquified retry suffixes (t3code/<hex>-N) as temporary worktree branches so auto-rename still applies after a partial bootstrap retry. - Re-derive startFromOrigin when a draft's mode flips via branch selection, and when a stored draft is reused for a different project member; the reused draft also re-derives its surface default instead of inheriting the store's hard-coded local reset. - Mobile waits for environment settings before auto-persisting a workspace selection, so the pre-config fallback can't freeze the wrong default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An explicit options.startFromOrigin passed without envMode was overwritten by the cross-project re-derivation. Guard on the option's presence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e4061d4. Configure here.
… reuse Passing only startFromOrigin no longer skips the env-mode re-derive (and vice versa) when a stored draft is reused for a different project member. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Implements the new-thread workspace model (plan): every new thread starts from the resolved default workspace mode instead of inheriting the previous thread's choices, and worktree threads pin a fresh remote base with visible provenance.
Surface-derived defaults
deriveThreadEnvModeFromSurfaceserver setting (default on). The attention test decides the default: surfaces attached to a visible local checkout (desktop app, loopback browser on a host-managed environment) default to the current checkout; detached surfaces (remote browser, mobile, relay/SSH environments) default to a fresh worktree. A browser reaching the primary server over LAN/domain counts as detached — the checkout lives on a machine the user can't see.defaultThreadEnvModeas the legacylocal | worktreeliteral so older clients still decode server-config snapshots; "Auto" is expressed by the new boolean rather than a third literal.chat.newLocalremains the explicit current-checkout exception (draft-only).projectThreadEnvModeOverrides, client setting) — the only sanctioned way a composer choice retrains the default. Slots into project scope when the settings-scope architecture lands.Fresh worktree bases without a blocking fetch
WorktreeBasePlanner(server): a fetch of origin within the 30s freshness window counts as fresh and pins immediately; concurrent fetches dedupe per repository; a failed fetch with a successful fetch within the last hour proceeds on the last-known remote commit (fail-visible,staleprovenance); with no usable data the bootstrap fails closed — it never silently falls back to a local ref.vcs.prefetchRemoteRPC so the send-time check almost always hits the fresh window.newWorktreesStartFromOriginnow defaults on.Provenance and registry
worktree.base-pinnedthread activity (base ref, commit sha, provenance, fetch time). The branch toolbar turns the promise into the fact post-send: "Worktree ·a1b2c3f", provenance on hover; stale pins get an error-tone activity.WorktreeRegistrypersists thread↔worktree records tostateDir/worktree-registry.json(registered at bootstrap, marked on thread deletion) — every worktree is attributable to a thread by construction, groundwork for lifecycle/GC tooling.t3code/<first-8-uuid-hex>), with server-side collision suffixing (uniquifyNewRefName).Composer UX
Verification
vp run typecheck,vp check, andvp run -r testall pass (full suite incl. server, web, mobile, desktop).🤖 Generated with Claude Code
Note
High Risk
Touches git bootstrap, default settings, and thread-creation flows across server, web, and mobile; incorrect surface or fetch logic could start threads in the wrong checkout or fail sends when remotes are down.
Overview
New threads no longer inherit workspace mode, branch, or worktree from the active thread or draft; they start from a resolved default (surface + settings + optional per-project override).
deriveThreadEnvModeFromSurface(default on) maps attached clients to local checkout and detached surfaces (mobile, remote browser, relay) to worktree;projectThreadEnvModeOverridesand the branch toolbar “Always for this project” action are the only ways to persist a project default.Worktree bootstraps now pin a remote base via
WorktreeBasePlanner(fresh fetch, deduped prefetch, stale-within-horizon, or fail closed), recordworktree.base-pinnedactivities, register paths inWorktreeRegistry, use thread-id-derived branch names with collision suffixing, and exposevcs.prefetchRemotefor composer-open warmup. Defaults flip tonewWorktreesStartFromOrigin: true; UI shows dirty-checkout hints, pinned SHA labels, and an Auto new-thread mode in settings.Reviewed by Cursor Bugbot for commit d31b1c8. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add worktree base pinning, a worktree registry, and surface-derived workspace mode defaults
WorktreeBasePlanner(WorktreeBasePlanner.ts), which deduplicates remote fetches per repository and resolves a base commit with provenance (fresh,stale, orlocal) using configurable freshness and usable-horizon windows.WorktreeRegistry(WorktreeRegistry.ts), a durable JSON-backed service that records worktree-to-thread associations with atomic writes and serialized access; thread deletion marks associated records as unowned.WorktreeBasePlanner.pinRemoteBaseto select the base commit for new worktrees, appends aworktree.base-pinnedactivity with provenance, and setsuniquifyNewRefName: trueso retries create a unique branch instead of failing.local/worktree) from the client surface (attached-checkoutvsdetached) or a per-project override stored in client settings, replacing hardcodedlocaldefaults.Autooption for surface-derived mode.vcsPrefetchRemoteWebSocket RPC and a background prefetch effect inChatViewContentthat warms freshness checks when composing a worktree draft withstartFromOrigin=true.newWorktreesStartFromOriginnow defaults totrue; new thread seed context no longer inherits mode, branch, or worktree path from the active thread.Macroscope summarized d31b1c8.