Problem or use case
For anyone whose normal workflow is "every session gets its own worktree", the new-session composer makes that a repeated manual ritual. Starting an isolated session today costs, per session:
- Click the git-branch chip in the composer footer tray (
data-testid="new-chat-landing-branch-chip", web/src/shell/NewChatDialog.tsx).
- Wait for the popover, then click the shuffle button (
new-chat-landing-branch-generate) to fill a name — or type one yourself.
- Dismiss the popover and get back to the prompt box.
The machinery to skip all of that already exists. generateBranchName() (NewChatDialog.tsx ~L2897) mints worktree-<8 hex> names, and there is already an effect (~L2950) that calls it automatically when the picked project stores use_worktree: true — set per project in ProjectSettingsDialog.tsx (~L374). So auto-generation is a supported, tested behavior; it is just gated behind per-project configuration.
That leaves two gaps:
- No global default. A user who wants worktree-per-session everywhere must flip
use_worktree on every project individually, and gets nothing for sessions started outside a project (the plain landing composer, which is the fastest path in and the one the ⌘K palette's "New chat" action lands on).
- The setting that would host this already exists and is empty-ish. Settings → Git ("Configure how Omnigent works with Git",
pages/SettingsPage.tsx ~L969) currently holds exactly one control, DefaultBaseBranchControl — a preference that only matters once you are creating a new worktree. The base branch for new worktrees is globally configurable; whether to create one at all is not.
The cost is small per session and large in aggregate: it is 2–3 clicks on the single most frequent action in the product, and it is friction precisely on the safe choice (isolated worktree) versus the unsafe one (agent runs directly in the main work tree).
Proposed solution
Add a global "Start new sessions in a new worktree" preference, defaulting to off (no behavior change for existing users), and honor it in the composer wherever a project config doesn't already say otherwise.
Settings. A switch in the existing Git section, stored with the same localStorage-preference shape as the base branch (lib/baseBranchPreferences.ts → readDefaultBaseBranch / writeDefaultBaseBranch). Sits directly above DefaultBaseBranchControl, which reads as its natural sub-setting.
Composer. Extend the existing opt-in worktree effect (NewChatDialog.tsx ~L2950) so its trigger is prefillConfig?.useWorktree === true || readDefaultUseWorktree(). Every guard that effect already carries stays exactly as-is, and they are the reason this is a small change:
- ref-guarded to fire at most once per settled workspace (
worktreeSeededForRef);
- only into an empty branch field (
branchName !== "" || prefilledBranch !== "" bails), so a typed name or an existing-worktree prefill is never clobbered;
- skipped for sandbox sessions and until the workspace is settled;
- only when the git-ness probe resolves and the workspace is a real repo (
hostWorktrees.some((w) => w.is_main)), so it is inert in a non-git directory.
Precedence. Project config wins over the global default in both directions — a project with use_worktree: true still auto-generates when the global default is off, and a project explicitly configured without it should not be overridden by the global on. That requires distinguishing "unset" from "off" in the project config, which today only ever stores an explicit true (ProjectSettingsDialog.tsx ~L191: "only an explicit ON is stored"). Simplest version that preserves the existing storage contract: treat absent as "inherit the global", and add an explicit off value only if someone asks for it.
Escape hatch stays. The generated name lands in the branch field, visible on the chip (worktreeLabel), and is fully editable — clearing it before send starts in the working directory as before. Nothing becomes unopt-outable per session.
Acceptance criteria
- Settings → Git has a "Start new sessions in a new worktree" switch, off by default, persisted across reloads.
- With it on, opening the new-session composer on a git working directory pre-fills a generated
worktree-* branch name with no clicks, and the chip reflects it.
- With it on, the pre-fill does not fire for: sandbox sessions, non-git working directories, a workspace that is already an existing worktree (branch prefill wins), or a branch name the user typed.
- Changing the working directory to another git repo re-seeds a fresh name once (existing ref-guard behavior).
- A project with
use_worktree: true behaves as it does today regardless of the global setting.
- Vitest coverage alongside the existing
NewChatDialog.projectPrefill.test.tsx / NewChatDialog.flow.test.tsx cases.
Alternatives considered
- Make auto-generation unconditional (no setting). Wrong default for anyone who deliberately runs in the main work tree, and it changes behavior silently for every existing user. It also creates real on-disk worktrees as a side effect of merely opening the composer.
- Ship it as a per-project default only, and improve project onboarding instead. Doesn't help the plain landing composer or the palette's "New chat" — the fastest and most-used entry points, which have no project.
- Keep it manual but reduce it to one click (a "New worktree" item on the chip, or auto-focus the branch input when the popover opens). Better than today, but it still scales with session count, and the generate button is already effectively that one click.
- Generate the branch server-side at session create when no branch is given. Moves the policy into the API and hides it from the UI — the user could no longer see or edit the name before sending, and the chip would show "No worktree" while a worktree was in fact created.
Harness
Not applicable
Platform or device
Not platform-specific
Harness mode
Not applicable
Expected reach
A substantial user segment
Authentication type
Not authentication-related
Problem or use case
For anyone whose normal workflow is "every session gets its own worktree", the new-session composer makes that a repeated manual ritual. Starting an isolated session today costs, per session:
data-testid="new-chat-landing-branch-chip",web/src/shell/NewChatDialog.tsx).new-chat-landing-branch-generate) to fill a name — or type one yourself.The machinery to skip all of that already exists.
generateBranchName()(NewChatDialog.tsx ~L2897) mintsworktree-<8 hex>names, and there is already an effect (~L2950) that calls it automatically when the picked project storesuse_worktree: true— set per project inProjectSettingsDialog.tsx(~L374). So auto-generation is a supported, tested behavior; it is just gated behind per-project configuration.That leaves two gaps:
use_worktreeon every project individually, and gets nothing for sessions started outside a project (the plain landing composer, which is the fastest path in and the one the ⌘K palette's "New chat" action lands on).pages/SettingsPage.tsx~L969) currently holds exactly one control,DefaultBaseBranchControl— a preference that only matters once you are creating a new worktree. The base branch for new worktrees is globally configurable; whether to create one at all is not.The cost is small per session and large in aggregate: it is 2–3 clicks on the single most frequent action in the product, and it is friction precisely on the safe choice (isolated worktree) versus the unsafe one (agent runs directly in the main work tree).
Proposed solution
Add a global "Start new sessions in a new worktree" preference, defaulting to off (no behavior change for existing users), and honor it in the composer wherever a project config doesn't already say otherwise.
Settings. A switch in the existing Git section, stored with the same localStorage-preference shape as the base branch (
lib/baseBranchPreferences.ts→readDefaultBaseBranch/writeDefaultBaseBranch). Sits directly aboveDefaultBaseBranchControl, which reads as its natural sub-setting.Composer. Extend the existing opt-in worktree effect (NewChatDialog.tsx ~L2950) so its trigger is
prefillConfig?.useWorktree === true || readDefaultUseWorktree(). Every guard that effect already carries stays exactly as-is, and they are the reason this is a small change:worktreeSeededForRef);branchName !== "" || prefilledBranch !== ""bails), so a typed name or an existing-worktree prefill is never clobbered;hostWorktrees.some((w) => w.is_main)), so it is inert in a non-git directory.Precedence. Project config wins over the global default in both directions — a project with
use_worktree: truestill auto-generates when the global default is off, and a project explicitly configured without it should not be overridden by the global on. That requires distinguishing "unset" from "off" in the project config, which today only ever stores an explicittrue(ProjectSettingsDialog.tsx~L191: "only an explicit ON is stored"). Simplest version that preserves the existing storage contract: treat absent as "inherit the global", and add an explicit off value only if someone asks for it.Escape hatch stays. The generated name lands in the branch field, visible on the chip (
worktreeLabel), and is fully editable — clearing it before send starts in the working directory as before. Nothing becomes unopt-outable per session.Acceptance criteria
worktree-*branch name with no clicks, and the chip reflects it.use_worktree: truebehaves as it does today regardless of the global setting.NewChatDialog.projectPrefill.test.tsx/NewChatDialog.flow.test.tsxcases.Alternatives considered
Harness
Not applicable
Platform or device
Not platform-specific
Harness mode
Not applicable
Expected reach
A substantial user segment
Authentication type
Not authentication-related