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 worktreeenter_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:
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:
| Value | Behavior |
|---|---|
"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:
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:
| Command | Description |
|---|---|
/worktree | List worktrees, with the current one marked |
/worktree list | Same 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
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.
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
The flag accepts four forms:
| Form | Behavior |
|---|---|
(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 URL | Fetches 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.
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.
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.
| Input | Description |
|---|---|
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
namethat 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.lockare all refused - Namespacing:
/in a name is a logical separator; on disk and in the branch name it flattens to+(feat/auth→ branchworktree-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_worktreefirst
exit_worktree
Leaves the worktree and restores the session's original working directory. The model chooses what happens to the worktree:
| Input | Description |
|---|---|
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.
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:
| Key | Behavior |
|---|---|
createHook | Runs 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 |
removeHook | Runs on exit_worktree remove, with COMMAND_CODE_WORKTREE_PATH and COMMAND_CODE_WORKTREE_NAME set. Owns deleting whatever createHook made |
sparsePaths | Makes new git worktrees sparse-checkout cones - fast checkouts in large monorepos |
symlinkDirs | Symlinks 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
Compare the results, keep the winner, remove the rest with /worktree remove approach-b.
Reviewing a PR in isolation
PR review
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:
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_worktreerefuses 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, andenter_worktreerestarts. Don't be afraid to exit withkeepand come back later. - Use
.worktreeincludefor.envfiles: 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,
sparsePathsandsymlinkDirsmake worktree creation dramatically cheaper - sparse checkouts plus a sharednode_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.
- Learn how Plan Mode interacts with write tools like the worktree pair
- Rewind in-worktree changes with checkpoints
- Join our Discord community for support