Git Worktrees

Command Code has first-class support for git worktrees - isolated checkouts of your repository that let a session work on a separate branch in a separate directory, without touching your main checkout.

There are three ways to use them, from most manual to most automatic:

  • /worktree - manage and switch worktrees from inside a session
  • --worktree / -w - launch a session directly into a worktree
  • enter_worktree / exit_worktree - let the model isolate its own work mid-session, when you ask for it

A regular session works in your checkout. That's usually what you want - but sometimes it isn't:

  • You want to try a risky refactor without disturbing your working tree
  • You want multiple sessions in parallel, each on its own branch, with no file conflicts between them
  • You want to review a PR in a real checkout while your own branch stays untouched
  • You want the agent to do scratch work in isolation and throw it away if it doesn't pan out

A worktree gives each of these its own directory and its own branch, backed by the same repository. Everything a session does in a worktree - file edits, shell commands, test runs - happens there, and your main checkout never changes.

Where managed worktrees live

Command Code keeps its worktrees outside your repository, under:

~/.commandcode/worktrees/<repo>/<name>/

The <repo> folder is named after your repository (plus a short hash of its path, so two repos with the same folder name never collide). Each worktree checks out a dedicated branch named worktree-<name>.

Keeping worktrees outside the repo means they can never nest inside each other, never show up in your project's file tree, and never need .gitignore entries.

What a new worktree is based on

By default, a fresh worktree branches from your repo's default branch on origin (Command Code fetches first, best-effort), so it starts from a clean state matching the remote. With no remote - or when the fetch fails - it falls back to your local HEAD.

You can change this with the worktree.baseRef setting in settings.json:

{ "worktree": { "baseRef": "head" } }
ValueBehavior
"fresh" (default)Branch from the remote default branch - a clean start matching origin
"head"Branch from your current local HEAD - carries unpushed commits and feature-branch state into the worktree

Copying gitignored files with .worktreeinclude

A worktree is a clean checkout, so untracked-but-needed files (.env, .env.local, local secrets) are absent. List them in a .worktreeinclude file at your repo root, using .gitignore syntax:

.env .env.local config/local-overrides.json

Files that match a pattern and are gitignored are copied into every new worktree. Tracked files are never duplicated - git already puts those in the worktree.

Manage worktrees from inside a session:

CommandDescription
/worktreeList worktrees, with the current one marked
/worktree listSame as above (ls also works)
/worktree <name>Create the worktree if needed, then switch into it
/worktree new <name>Explicit create + switch (create and add also work)
/worktree remove <name>Remove a managed worktree (rm also works)

List worktrees

# In Command Code session /worktree Worktrees: . (main) [main] ○ ~/.commandcode/worktrees/myrepo-a1b2c3d4e5f6/experiment [worktree-experiment] Switch with /worktree <name>.

Switching respawns the session

Switching worktrees with /worktree <name> starts a fresh session in the target worktree: Command Code creates the worktree up front (so failures surface immediately), then respawns itself with --worktree <name>. Your current session isn't lost - it stays resumable, and its transcripts remain keyed to its own directory.

Info

A worktree that already exists is reused, not recreated - /worktree <name> is idempotent, so switching back and forth between worktrees is safe.

Removing worktrees

/worktree remove <name> deletes the worktree directory, and - for managed worktrees under ~/.commandcode/worktrees/ - its worktree-<name> branch too. Two guardrails apply:

  • You cannot remove the worktree you're standing in - switch away first
  • A worktree with uncommitted changes is refused; commit or stash first, or remove it manually with git worktree remove --force

Start a session directly inside a worktree, before the TUI even boots:

Launch in a worktree

cmd -w # bare: generate a friendly name cmd -w experiment # by name (created if missing, reused if not) cmd -w /path/to/worktree # an absolute path, used verbatim cmd -w "#1234" # check out PR #1234 into a pr-1234 worktree cmd -w https://github.com/acme/repo/pull/1234 # PR URL works too

The flag accepts four forms:

FormBehavior
(bare -w)Generates a friendly name and creates a fresh worktree
<name>Creates ~/.commandcode/worktrees/<repo>/<name> on branch worktree-<name>, or reuses it if it exists. Names are sanitized into a safe form
<absolute path>Uses that path as the worktree location verbatim
#N or a GitHub PR URLFetches pull/N/head from origin into a pr-N worktree - a real local checkout of the PR

Because the switch happens before the session boots, everything downstream - session storage, tools, the git header, memory/skills discovery - transparently operates inside the worktree. A banner above the TUI shows which worktree you entered and its path.

Info

#1234 needs quoting in most shells (cmd -w "#1234") - an unquoted # starts a comment.

What happens on exit

When you quit a session that freshly created its worktree via -w:

  • Clean worktree (no changes, no commits): removed automatically - no leftovers from an experiment that never went anywhere
  • Worktree with work, or an explicitly named one: you get a keep/remove prompt. Keep is the default, and Esc or Ctrl+C always keeps - an accidental keypress can never discard work

Worktrees that already existed before the session are always left in place. Non-interactive runs (cmd -p -w …) never auto-clean either, so scripted and agent-driven runs leave their worktrees for you to inspect.

Sometimes you want the model to isolate its own work - "try this migration in a worktree so my checkout stays clean." For that, Command Code exposes a pair of tools the model can call mid-session, no respawn involved.

Info

The model uses these tools only when you explicitly ask for a worktree (or for work "in isolation"). Ordinary feature work and bug fixes happen in your checkout as usual - a plain git branch does not need a worktree.

enter_worktree

Creates (or resumes) a managed worktree and switches the session's working directory into it. All subsequent file and shell tools operate there.

InputDescription
name (optional)Worktree name - max 64 characters; letters, digits, dots, underscores, dashes; / allowed as a namespace separator (e.g. feat/auth). A friendly name is generated when omitted

Behavior worth knowing:

  • Create-or-resume: an explicit name that already exists resumes that worktree - same directory, same branch, work intact. A generated name is always fresh; collisions are regenerated, never resumed
  • Validated, not rewritten: an invalid name is rejected with an actionable error rather than silently changed - the model's idea of the worktree name always matches reality. ./.. segments, Windows-reserved device names, and names ending in .lock are all refused
  • Namespacing: / in a name is a logical separator; on disk and in the branch name it flattens to + (feat/auth → branch worktree-feat+auth), so a name can never create nested directories or a git ref conflict
  • One at a time: entering while the session is already inside a tool-entered worktree fails - the model must exit_worktree first

exit_worktree

Leaves the worktree and restores the session's original working directory. The model chooses what happens to the worktree:

InputDescription
action (required)"keep" preserves the directory and branch for later (the same name resumes it); "remove" deletes both
discard_changes (optional)Must be true to remove a worktree that has uncommitted files, unmerged commits, or unverifiable state. No effect with "keep"

Removal is fail-closed:

  • Before an unconfirmed remove, the worktree is checked for uncommitted files and commits beyond the entry baseline - if any exist, removal is refused and the model is told to confirm with you first
  • If the state cannot be verified (a git failure, a missing baseline), removal is also refused - unknown never means "clean"
  • Only an explicit discard_changes: true - which the model is instructed to send only after confirming with you - overrides the gate

exit_worktree operates only on the worktree this session entered via enter_worktree. Worktrees created manually, with --worktree, with /worktree, or in a previous session are untouchable - the tool never infers ownership from your cwd or directory layout.

Info

If you quit the CLI while still inside a tool-entered worktree, you get the same keep/remove exit prompt as a --worktree session - nothing is silently discarded.

Permission model

Both tools are treated as write tools:

  • Hidden in Plan Mode - they mutate git state and change the process working directory, which defeats read-only exploration. See Plan Mode
  • Auto-approved in Auto-Accept Mode - their blast radius is repo-scoped git state, the same class of change as the file edits the mode already accepts
  • Never granted to sub-agents - a sub-agent switching the parent session's working directory out from under it would corrupt every sibling tool call, so only the top-level session can enter or exit a worktree

Custom isolation backends

For non-git isolation - containers, custom sandboxes - a worktree.createHook in settings.json replaces the git backend entirely:

{ "worktree": { "createHook": "./scripts/make-sandbox.sh", "removeHook": "./scripts/drop-sandbox.sh", "sparsePaths": ["packages/api", "shared"], "symlinkDirs": ["node_modules"] } }
KeyBehavior
createHookRuns instead of git, with COMMAND_CODE_WORKTREE_NAME in its environment. Its last stdout line must be the absolute path of the prepared directory. No git branch is created
removeHookRuns on exit_worktree remove, with COMMAND_CODE_WORKTREE_PATH and COMMAND_CODE_WORKTREE_NAME set. Owns deleting whatever createHook made
sparsePathsMakes new git worktrees sparse-checkout cones - fast checkouts in large monorepos
symlinkDirsSymlinks heavyweight directories (like node_modules) from the main worktree instead of rebuilding them

Hook-created directories have no git baseline, so exit_worktree treats their state as unverifiable - removing one always requires discard_changes: true.

Parallel experiments

Run several sessions at once, each in its own worktree, with zero file conflicts:

Parallel sessions

# Terminal 1 cmd -w approach-a # Terminal 2 cmd -w approach-b

Compare the results, keep the winner, remove the rest with /worktree remove approach-b.

Reviewing a PR in isolation

PR review

cmd -w "#1234"

You get a real checkout of the PR in its own directory - run the tests, poke at the code, ask Command Code to review it - while your own branch stays exactly where you left it.

Agent-initiated scratch work

Ask for isolation in plain language:

Try migrating the config loader to zod in a worktree - I don't want my checkout touched until we know it works.

The model calls enter_worktree, does the work there, and when you're done calls exit_worktree - keep if the experiment is worth continuing (re-enter it later by name), remove if it isn't. The fail-closed gate ensures nothing with real work in it is deleted without your say-so.

  • Your main checkout is never touched: Worktree work happens on worktree-<name> branches in separate directories; merge back only when you choose to.
  • Keep is always the safe default: The exit prompt defaults to Keep, Esc/Ctrl+C keep, and exit_worktree refuses to remove anything with work in it - or anything it can't verify - without explicit confirmation.
  • Reuse is cheap: The same name always resumes the same worktree, across /worktree, -w, and enter_worktree restarts. Don't be afraid to exit with keep and come back later.
  • Use .worktreeinclude for .env files: Fresh worktrees don't have your gitignored local config; list what's needed once and every new worktree gets it.
  • Set "baseRef": "head" if your worktrees should build on your current branch state rather than a clean copy of the remote default branch.
  • In monorepos, sparsePaths and symlinkDirs make worktree creation dramatically cheaper - sparse checkouts plus a shared node_modules.
  • Left-behind branches are harmless: If a worktree-<name> branch ever survives a removal, the next create with that name simply reuses or resets it.