rationale.md
# wt-switch-create design rationale
The design of the skill's create-and-enter flow, and the verified harness
behavior behind it. The flow is error-driven; predictive guards and extra
routes were tried and cut.
Every claim here was verified against primary sources (2026-06-11 to
2026-06-17): Claude Code 2.1.173, with the path-entry and working-directory
logic re-confirmed live and against the 2.1.177 binary, plus official docs at
code.claude.com/docs; and wt v0.57.0-16-g371d28662 (live runs in a scratch
repo). Entry — the confirmation, both `EnterWorktree` routes, and what each
leaves behind at exit — was re-run live on 2026-07-28 against Claude Code
2.1.220 and wt v0.69.2. The confirmation hook and `cd`-before-entry for another
repo were verified live on 2026-09-16 against Claude Code 2.1.273 (#4149).
Hook-based exit cleanup was checked live on Claude Code 2.1.308 on 2026-10-22.
Leaving a worktree session with `ExitWorktree` before path entry was verified
live on 2026-11-05 against Claude Code 2.1.316 and wt v0.81.0.
Re-verify against current versions before relying on a specific behavior; the
*shape* of the argument should outlive the details.
Binary symbol names are deliberately omitted — they re-minify every build.
## The design
Steps are numbered as in `SKILL.md`; step 1, picking the branch name, needs
no rationale.
2. `EnterWorktree({name})` in this repo. Worktrunk's `WorktreeCreate` hook runs
`wt switch --create` for a new branch or `wt switch` for an existing one,
creating its checkout if needed. Passing no `path` keeps M2's confirmation
from firing. It fails from a session that already entered a worktree, and
it has no repo targeting.
3. Otherwise `wt -C <repo> switch --create <branch> --no-cd --format=json` in
Bash, then `ExitWorktree({action: "keep"})`, then, for another repo,
`cd <repo>`, then `EnterWorktree({path})`.
`wt` solves repo targeting (`-C` works from anywhere), existing-branch
handling (rerun without `--create`), and machine-readable output (`.path`
on stdout, status on stderr). Creating in another repo is fine; only
*entering* the result is constrained, and both constraints read the
repository from the cwd: the tool re-roots only within it (M2), and
worktrunk's hook answers the confirmation only for that repository's
worktrees ("The confirmation hook"). The `cd` satisfies both. Entry that someone declined
ends there, with the worktree left unentered (M2).
Leaving first lifts the stricter gate a session already in a worktree is
held to (M2, "Why leave before entering"), and does nothing for a session
in none. Leaving also moves the cwd and the session's base cwd (M1) back
to where they stood when the current worktree session began (a switch
within a session keeps that point; leaving and entering again resets it).
That is normally in the same repository as the worktree just left, so
with no repo argument step 3 enters from there. A `cd` back into the
worktree just left would reset under the default sibling layout unless an
`additionalDirectories` entry covers it, and after a `cd` into the new
worktree `EnterWorktree` would refuse it as the current directory.
On a tool error (or a denial with no user behind it), the session can still
work there iff the path sits
inside a directory it's allowed in (an `additionalDirectories` entry). A
single `cd <path>` discovers which: it sticks when reachable, resets when
not. Reachable → work in place. Unreachable → escalate, because the agent
can't enlarge that set itself: ask the user to add the repo or a parent
(e.g. `~/workspace`) to `additionalDirectories`, or `/add-dir <path>`. A
one-line, set-once handback, not a silent degrade. A `cd <repo>` that
resets reaches the same handback without attempting entry, which would only
stop at a confirmation the hook can't answer and then fail.
Two independent harness facts underlie this — re-root is repo-scoped, and `cd`
persistence is working-directory-membership-scoped — detailed below.
## Why creation is unconditional
The recurring failure: handed a research or read-only task, the model decided
it didn't need isolation and skipped creation. Two wordings fed that. Scope's
"authorizes" read as permission, inviting a should-I test (user-level rules
that gate worktree creation behind an explicit request answer it "no"); an
ordering-only lead ("steps 1–3 come first") doesn't bind a model that decided
the steps don't apply. So the lead makes the invocation the explicit request
itself, and Scope states bounds rather than permission. Don't reintroduce
either wording.
## wt CLI and git behavior (all live-tested)
- `wt switch --create <branch>` exits 1 with `✗ Branch <branch> already
exists` whenever the branch exists, with or without a worktree. Its own
hint names the fix: rerun without `--create`.
- `wt switch <branch>` (no `--create`) exits 0 for an existing branch: it
creates the worktree if missing (`"action":"created","created_branch":false`)
or re-enters it (`"action":"existing"`). Per `wt switch --help`: "Without
--create, the branch must already exist."
- Every `--format=json` variant carries `path` (absolute). Only the JSON goes
to stdout; all human-readable status, including hook output, goes to stderr —
which is what makes `.path` extraction safe.
- `wt remove`: dirty worktree → refuses (exit 1, hints `--force`); clean but
unmerged commits → removes the worktree, keeps the branch, hints
`wt remove -D`; clean and merged → removes worktree and branch.
- The git stash is per-repo, shared across worktrees: `git stash push -u` in
one worktree pops cleanly in another via `git -C <path> stash pop`,
untracked files included — the mid-session carry-across in the skill's
creation step.
## Claude Code behavior
A session works wherever its cwd is. Two mechanisms move it, and they *compose*:
`cd` moves the cwd (and so which repo `EnterWorktree` sees); `EnterWorktree`
re-roots within the repo the cwd is in. All verified live and against the
2.1.177 binary.
### M1 — `cd` (shell cwd)
Moves the shell cwd; the statusline and tool-path relativization
(`Write(foo/bar.py)`) follow it.
- **Gate:** the path must sit inside a configured working directory — the
session's base cwd plus every entry in `permissions.additionalDirectories`
(settings.json), `--add-dir` (launch), or `/add-dir` (mid-session).
- Inside → `cd` persists across Bash calls. Outside → the harness snaps it back
and appends `Shell cwd was reset to <original>` to the result.
- **Repo-blind:** it checks only the path's location against that set, never
which git repo owns the path. A different repo's worktree under `/tmp` is
reachable when `/tmp` is configured.
- Within a *single* Bash call, `cd X && cmd` always works; the reset happens
only *between* calls. (Subagent threads reset between every call.)
### M2 — `EnterWorktree({path})` (re-root)
Formally re-roots: sets the session's worktree home (tracked for exit) and its
cwd.
- **Gate:** a worktree of **the repo the current cwd resolves to**, by session
state:
- **Plain / first-entry session** → any worktree *registered to that repo*
(`git worktree list`), anywhere on disk; in a multi-repo workspace, also
one registered to a repo nested inside it.
- **Already in a worktree-session, or a pinned agent** → only under that
repo's `.claude/worktrees/`; rejects even same-repo siblings.
`ExitWorktree({action: "keep"})` ends a worktree session, keeps its
worktree, and moves the cwd and the base cwd (M1) back to where they
stood when that worktree session began, after which the first bullet
applies again. In a subagent with a cwd override (`isolation: "worktree"`
or an explicit `cwd`) it refuses instead (`ExitWorktree cannot be called
from a subagent with a cwd override …`; read from the 2.1.316 binary, not
run live), so a pinned agent stays under this gate.
- **cwd outside any git repo** → refuses entirely.
- **Confirmation:** the safety check runs on the two facts the call carries —
a `path` argument, and a target outside the project's `.claude/worktrees/` —
and asks before anything else runs, the dialog reading "permission-root
relocation to `<path>` — a model-supplied worktree outside
.claude/worktrees/". Yes/no only: no always-allow, nothing persisted after a
yes, and `permissions.allow` entries for `EnterWorktree` (bare, `(*)`, or a
path glob) don't suppress it. It asks wherever the session can prompt
(`default`, `acceptEdits`, `auto`); `bypassPermissions` allows without
asking. A `PermissionRequest` hook's `allow` decision answers it in place of
the click, and a session that can't prompt runs the same hooks and denies
only when none decides (hooks docs, "PermissionRequest"). Worktrunk's hook
is one ("The confirmation hook").
`EnterWorktree({name})` passes no `path`, so it never asks.
- **Reading the failure:** step 3 splits tool errors from denials
structurally rather than by parsing the denial's wording. The tool's own
rejections are verbatim and graceful (`Cannot enter …`), so they key the
recovery; a denial of the call defaults to being the user's answer, the
no-user case an exception keyed on the denial saying the session couldn't
prompt. The default falls on the safe side because denial texts don't
classify reliably: a typed "no" arrives as the generic `The user doesn't
want to proceed with this tool use`, naming neither the tool nor the
confirmation, and in blind tests every wording that asked the agent to
recognize it — branches labeled by who refused, "the denial reports a
decision", even that string quoted as an example — sent the agent into the
recovery, into the worktree the user had just declined. A recovery that
follows a denial invites that routing, so the denial branch leads with
stopping. The only decision that reaches step 3 is that answer — a
`permissions.deny` entry for `EnterWorktree`, with or without an argument
pattern, drops the tool from the session instead, so there is no call to
deny.
- Rejections are graceful and side-effect-free (nothing is created). The ones
the skill's own flow can produce, verbatim (others exist — a worktree locked
by another running session, a prunable registration):
- registered-check: `Cannot enter worktree: <path> is not a registered
worktree of <repo>. Run 'git -C <repo> worktree list' …`
- managed-location: `Cannot enter worktree: <path> is not under
<repo>/.claude/worktrees. Switching from this session is limited to
worktrees managed by Claude Code …`, or, in a repo with no
`.claude/worktrees/` at all, `Cannot enter worktree:
<repo>/.claude/worktrees does not exist, so <path> cannot be a worktree
managed by Claude Code.`
- current directory: `Cannot enter worktree: <path> is the current working
directory.`
- main working tree: `Cannot enter worktree: <path> is the main working
tree, not a linked worktree.`, when the user names the branch checked out
there and the rerun without `--create` returns its path.
- no repo: `Cannot enter an existing worktree: the current directory is not in
a git repository.`
The repo is read from the cwd, so `EnterWorktree` never moves you to a different
repo on its own — it re-roots within whatever repo you're already standing in.
To re-root into *another* repo, `cd` into it first, then `EnterWorktree`.
Verified: from a worktrunk session, `cd` into a prql worktree under `/tmp`, then
`EnterWorktree` re-rooted within prql.
### The confirmation hook
The plugin's hooks run the hidden `wt config plugins claude hook`, which reads
the hook payload. For `PermissionRequest` it prints an `allow` decision when
the call is `EnterWorktree` and its `path` is a worktree of the repository the
payload's `cwd` is in, at the path the `worktree-path` template gives its
branch. Anything else leaves stdout empty, which leaves the dialog (or, where
no dialog can show, the denial) in place. The tool's own validation still runs
after an approval.
The rule extends Claude Code's exemption rather than overriding its check.
Claude Code enters a worktree under `.claude/worktrees/` without asking because
that location is its own convention, whichever name the model picked;
worktrunk's template location is the same kind of convention, and in the
default layout it holds every worktree `wt` creates. A worktree registered
anywhere else still asks, so a `git worktree add` into an arbitrary directory
(approvable in auto mode or by an allow rule) doesn't carry the permission root
there unconfirmed. The rule is not a boundary against a session that rewrites
the repository's own git config: `core.worktree`, `worktrunk.default-branch`,
and the remote URL all feed the template, and a session able to run those
commands can already write wherever an entry would let it.
The payload's `cwd` follows a shell `cd`, which is why step 3 `cd`s into
another repo before entering. The check lives in `wt` because it is
`is_worktree_at_expected_path`, the same test behind `wt list`'s
`branch_mismatch`; a script over `wt list --format=json` would inherit that
command's user config, where `list.full` adds CI fetches and LLM summaries
(33s measured on a 54-worktree repository) and `list.json-schema` and `list.branches` change
the output's shape. A `wt` too old to have the subcommand fails it, and the
hook command's `|| true` leaves the dialog as it was before the hook existed.
The approval shares one hook with the 💬 marker, setting the marker only when
it doesn't approve, because Claude Code runs all matching hooks in parallel. A
separate `EnterWorktree` entry would still fire the catch-all marker hook, and
the launch worktree would read 💬 while the session works on. With one hook,
an approval skips the marker and every other permission request sets it as
before. The `permission_prompt` notification can't set it either: Claude Code
sends it only after a shown prompt has waited about six seconds.
Verified live in `--permission-mode default` sessions: a same-repo entry, and a
cross-repo entry after `cd`, both reported "Allowed by PermissionRequest hook"
with no dialog, and the launch repo's marker still read 🤖 afterwards. The same
cross-repo call without the `cd` showed the dialog and set 💬, and so did a
worktree registered off the template path. An approved entry into the main
worktree, which sits at the default branch's expected path, was still refused
by the tool.
### How they compose
Whether you can work in — or re-root into — a repo reduces to whether your cwd
can be there, which is `cd`'s gate:
| Target | cwd reachable? | Result |
|---|---|---|
| Same repo (incl. its sibling worktrees) | always | `EnterWorktree` re-roots directly |
| Another repo under a configured dir (`~/workspace`, `/tmp`) | yes | `cd` in → working there; from a plain session, `EnterWorktree` re-roots within it too |
| Another repo outside every configured dir | no | unreachable — add it (or a parent) to `additionalDirectories`, or `/add-dir` |
| Outside any repo | n/a | no re-root possible |
`additionalDirectories` is the one master gate: once a repo, or a parent like
`~/workspace`, is in it, the session can `cd` into that repo's worktrees and both
work there and re-root within them. This is load-bearing for the same-repo path
too: a sibling worktree is registered to the repo, so `EnterWorktree` from a
plain session re-roots into the sibling `wt` creates. The agent **cannot**
enlarge that set itself (`/add-dir` is user-typed; the only automatic add is a
narrow symlink-resolving-to-the-same-cwd fixup), so a repo reachable by neither
the cwd nor config is a genuine handback to the user.
### Re-rooting keeps the launch repo's instructions
Re-rooting into another repo changes the cwd but not the instruction context.
Verified 2026-10-10 against Claude Code 2.1.296 with three headless sessions.
Two launched in another repo, then ran `cd` plus `EnterWorktree({path})` or
`cd` plus `EnterWorktree({name})`; each context still held the launch repo's
root `AGENTS.md` and auto-memory index, with nothing from the new repo. The
third, started in the worktree, loaded the new repo's. Nested instruction files of the
new repo still load lazily when a file in their directory is read. Both routes
run the same post-entry code in the binary, which clears the memo of loaded
instruction files but doesn't rebuild the root block, which reloads only at
session start and compaction; the entry confirmation's "loads project
configuration (CLAUDE.md, settings)" doesn't hold for it. The `name` route can't
cross repos anyway: the `WorktreeCreate` hook `cd`s to `$CLAUDE_PROJECT_DIR`,
which stays the launch repo, so the worktree lands there.
So with a repo argument, the skill has the agent read the new root files after
entry. That fixes the instructions only; the launch repo's root file stays in
context, as does its auto-memory.
### Why `--no-cd`
The Bash tool is not a bare shell: Claude Code replays the user's shell startup
from a snapshot, so a user who installed wt shell integration runs the `wt`
wrapper function inside the tool. wt then runs with integration active, and a
plain `wt switch` hands the wrapper a cd directive that moves the tool's cwd — a
second, untracked re-root racing `EnterWorktree`. `--no-cd` skips the directive,
so `EnterWorktree` stays the single re-root. Verified: without `--no-cd`,
`wt switch <branch>` moved the session and the new cwd persisted to the next
Bash call. Where the user never installed integration (a fresh shell, CI) the
wrapper is absent and wt cannot cd regardless, so `--no-cd` is load-bearing on an
integrated machine and a no-op elsewhere. Don't drop it.
### What `EnterWorktree({name})` costs, and where it stops
The `name` route is worth having because it never asks M2's confirmation, with
or without worktrunk's hook, and it creates and enters in one call. The same
plugin hook backs `isolation: "worktree"` agents, so the worktree it produces is
the one `wt` would have made either way. Three properties come with it:
1. **Exit asks whether to keep or remove a hook-created worktree.** Claude
Code 2.1.308 cannot infer unpushed commits in a worktree returned by a
`WorktreeCreate` hook. Even an untouched session gets a Keep / Remove
prompt, with Keep selected by default. This also applies when the hook
reopens an existing worktree. Keep leaves the checkout and branch intact;
Remove invokes `WorktreeRemove`, which uses normal `wt remove` safeguards.
2. **It enters an existing branch's worktree.** The dispatcher uses `wt switch`
without `--create`, creating the checkout if needed.
3. **It only works on a session's first entry, and only in its own repo.** From
a session already in a worktree it returns "Already in a worktree session.
Pass `path` to switch into another existing worktree", and it has no `-C`.
Step 3 handles repo targeting and a session's later entries. Other hook
failures surface directly, with no git fallback: the hook replaces the default
git behavior. The hook contract (stdout's last non-empty line must be an
existing directory) stays the hook's business, since the skill reads only the
tool result.
### Why escalate instead of grinding through absolute paths
The original incident worked a cross-repo task via absolute paths from a session
rooted elsewhere: every `cd` into the worktree reset, so each command needed an
absolute prefix, and the session never gained the worktree cwd. It produced
correct output but read as a failure. File tools (absolute paths) and `git -C`
are cwd-independent, so the work is *possible* that way — but it is friction the
user shouldn't absorb when the fix is one line of config.
So when the worktree is unreachable, the skill escalates with the concrete fix
rather than degrading silently. This is not a predictive guard: the skill
doesn't refuse to create cross-repo and doesn't guess reachability. It creates,
attempts entry, and lets a single `cd` reveal reachability — the escalation
fires only on an actual reset. Cheap to attempt, and the handback is actionable
and durable (a `~/workspace` entry, set once, covers every future cross-repo
task).
### Why leave before entering
Step 3 used to enter without leaving. A session already in a worktree, which is
the usual way to reach step 3 since step 2 fails exactly there, then always took
the managed-location rejection and the `cd` recovery: in one user's recorded
sessions over 45 days, 14 of 16 invocations from inside a worktree went that
way. The `cd` leaves the session's tracked worktree on the old one. Claude
Code's Bash guard for worktree sessions keeps judging commands against it and
names it when it refuses one ("run them from <old worktree>"), and session
exit and `ExitWorktree` both act on it. Leaving costs one call, keeps the old worktree on
disk, and makes the next entry a first entry. Verified live: from a session in
one `wt` worktree, entering a sibling by path failed with the managed-location
rejection, and after `ExitWorktree` the same call succeeded; entering another
repo's worktree likewise succeeded after leaving and `cd` into that repo, and a
session launched with `--worktree` left and entered the same way. With no repo
argument and no `additionalDirectories` entry, entering from where
`ExitWorktree` left the session succeeded, while a `cd` back into the old
worktree reset.
Leaving would also let step 2's name route work again, but its
`WorktreeCreate` hook creates in `$CLAUDE_PROJECT_DIR`, the launch repo
("Re-rooting keeps the launch repo's instructions"), so after an entry into
another repo's worktree a retry would create the worktree in the wrong repo.
`wt switch` creates it in the repo the session is working in.
## Known limits (deliberate)
- Another repo is reachable only through `additionalDirectories` ("How they
compose", above); outside it the skill escalates for the one-line config add
rather than degrading to absolute-paths mode.
- A pinned agent can't even re-enter a *same-repo* sibling worktree (the
stricter `.claude/worktrees/` check), and `ExitWorktree` refuses to run in
it; it lands in the same reachability test and the same escalation.
- Where the plugin's hooks don't run, or the worktree is off the
`worktree-path` template (an existing branch whose worktree lives elsewhere,
a detached worktree), step 3's entry asks the user to confirm, once per call,
and a background session waits at it until someone attaches (M2).
`permissions.allow` can't answer it.
- A subagent's `cd` doesn't carry to its next call, and it gets no reset notice
(M1), so from a subagent step 3 can't enter another repo's worktree: the
entry reaches the hook with the subagent's own repo as `cwd`.
SKILL.md
---
name: wt-switch-create
description: Create a new worktrunk worktree (optionally in another repo) and switch this session's working directory into it. Use when launching a session that should work in its own worktree.
argument-hint: "[<branch>] [<repo>] [-- <task>]"
license: MIT OR Apache-2.0
compatibility: Requires the `wt` CLI (https://worktrunk.dev)
---
Arguments: `$ARGUMENTS`. Grammar: `[<branch>] [<repo>] [-- <task>]`.
- **branch** — optional; the branch name for the new worktree. When omitted,
pick one (step 1 below).
- **repo** — optional path or name; create the worktree in this repo instead
of the session's current one.
- **task** — optional; what to do inside the new worktree. No task means enter
the worktree and wait.
Tokens before the `--` are the branch and/or repo. A path-shaped token
(starting with `/`, `~`, `./`, or `../`) is the repo. A bare name can be
either, so make an informed guess. It is the branch when the current repo
already has a branch by that name, as it is for `wt switch` itself. Otherwise
it is the repo when a git repository by that name exists where the user keeps
repos (beside the current repo, or in a workspace directory like
`~/workspace`), and the branch when none does (`docs` is a branch, not the
current repo's `docs/` directory). A name read as the repo carries its absolute
path forward: that path, not the bare token, is the `<repo>` in step 3. Two
tokens that both read as branches don't fit the grammar — ask. Without a `--`,
judge where the task starts: leading tokens that read as a branch name
(`fix-auth`) or a repo are consumed as such, and the rest is the task;
otherwise the whole input is the task (`fix the parser bug` has no
branch-shaped lead — all task).
```
/wt-switch-create my-feature -- fix the parser bug
/wt-switch-create -- fix the parser bug
/wt-switch-create my-feature ~/workspace/other-repo -- fix the parser bug
/wt-switch-create my-feature
```
## What to do
Creating the worktree comes first on every invocation, before any other work.
The invocation is itself the explicit request to create it; a research or
read-only task gets one all the same.
<!-- Maintainers: rationale.md (same directory) covers the harness rules and
design choices behind this — read it before re-adding guards or routes. -->
1. **Pick a new branch name** if none was given: short, from the task and
consistent with existing worktree names, or, mid-session, from the work
being moved; with nothing to derive from, ask.
2. **With no repo argument, create and enter in one call:**
`EnterWorktree({name: "<branch>"})`. Worktrunk's `WorktreeCreate` hook runs
`wt switch --create` for a new branch or `wt switch` for an existing one,
creating its checkout if needed. The result is an ordinary `wt` worktree,
and the user sees no confirmation prompt. On success, do the task (or,
with no task text, confirm it's ready and wait).
Mid-session, carry uncommitted work across: `git stash push -u` before the
`EnterWorktree` call, then `git stash pop` once the session is in the new
worktree, which is after step 3 if this call fails. Entering re-roots the
session into the new worktree, and the stash is shared across worktrees. If
step 3 hands back without the session in the new worktree, restore the work
where it came from first (`git -C <that worktree> stash pop`).
3. **Otherwise create it with `wt` and enter by path.** Two cases reach here: a
repo argument, which step 2 can't target, and a failed step 2, whose error
says `Already in a worktree session`. Create with a `Bash` call (omit
`-C <repo>` for this repo):
```
wt -C <repo> switch --create <branch> --no-cd --format=json
```
Stdout is JSON whose `path` field is the worktree's absolute path (status
lines go to stderr). On `Branch <branch> already exists`: if the user named
the branch, rerun without `--create` (it enters the branch, creating its
worktree if missing); if step 1 picked the name, pick another and rerun. Any
other failure (not a git repo, invalid name): report it and stop.
Next, call `ExitWorktree({action: "keep"})`. This invocation is the user
asking to move the session, which includes leaving the worktree it is in,
so make the call even when you're unsure the session is in one. A session
in a worktree can enter only one under its repository's
`.claude/worktrees/`; leaving lifts that limit, keeps the old worktree and
its branch on disk, and returns the session to the directory it was in
before it entered. Where there is nothing to leave (no worktree session, or
an agent pinned to its own worktree), the call returns an error and changes
nothing; carry on. If the session did leave a worktree
and the entry below doesn't happen, say so when you hand back: name the
worktree it left, still on disk, and where the session is now.
With a repo argument, `cd <repo>` next, in its own `Bash` call:
`EnterWorktree` re-roots only within the repository the cwd is in, and
worktrunk's `PermissionRequest` hook answers the confirmation that call
asks for only for a `wt` worktree of that same repository. If that `cd`
reports `Shell cwd was reset`, the repo is unreachable: skip the entry and
hand back as in **Unreachable** below. With no repo argument, don't `cd`:
leaving put the session back in this repository, and a `cd` into the
worktree it left can reset.
Then call `EnterWorktree({path: "<path from the JSON>"})`.
With a repo argument, once the session works in the new worktree (entered,
or reached by `cd` in the recovery below), read whichever of its root
`CLAUDE.md` and `AGENTS.md` exist before the task. Claude Code keeps the
launch repo's root instructions in context and loads only the new repo's
nested ones, so the new repo's root files supersede the launch repo's for
the rest of the session.
- **Accepted** → the session is re-rooted in the worktree. Do the task (or,
with no task text, confirm it's ready and wait).
- **Tool error** — the tool ran and returned an error (`Cannot enter
worktree: …`) → graceful; nothing moved, and one recovery covers them
all. One cause is an agent pinned to its own worktree, which can
enter only under its repo's `.claude/worktrees/`, excluding even a
same-repo `wt` sibling. The recovery test is whether you can `cd` into
the worktree, which works when it's inside an allowed directory. So
`cd <path>` and read the result:
- no `Shell cwd was reset` notice → it stuck; the worktree is reachable.
Work there, but a bare `cd` is not a tracked re-root, so the cwd can
revert to the session's launch worktree across turns (and in spawned
subagents); pin commands with `git -C <path>` / `wt -C <path>` rather
than trusting the `cd` to persist.
- `Shell cwd was reset` → **Unreachable.** Stop and ask the user to make
the directory whose `cd` reset reachable: add a parent that holds both
the repo and its worktrees, like `~/workspace`, to
`permissions.additionalDirectories` (durable, every session), or run
`/add-dir` on it (this session). Then continue from that `cd`. Don't
grind through absolute paths with `cd` resetting on every command.
- **Denied** — the call itself was refused, with no tool error → however
the denial is worded, it is the user's answer to the confirmation Claude
Code shows for entering a worktree outside `.claude/worktrees/`, unless
there was no user to ask (the denial says the session couldn't prompt),
which decides nothing — take the recovery above. On the user's answer:
the worktree `wt` just created still exists; only the entry didn't
happen. `cd` back out of the repo you moved into, if any, then report
the worktree's path and ask how to proceed, since reaching it through
`cd` would override that answer.
## Cleanup
The worktree is a normal worktrunk worktree: it shows up in `wt list` and is
merged or removed with `wt merge` / `wt remove <branch>` like any other. Don't
remove it unprompted.
A worktree from step 2 that the session never touched — no changed files, no
commits — is cleaned up when the session ends, branch included; anything
written into it keeps it. A worktree from step 3 always stays, as does a
worktree the session left in step 3. If the user asks
to leave mid-session, `ExitWorktree({action: "keep"})` returns the session to
its original directory;
`ExitWorktree` cannot remove a worktree entered by `path`, so removing one of
those is always `wt remove <branch>`.
## Scope
The command's mandate is ONE worktree (in the named repo, if one was given)
and the requested task inside it. Commits, pushes, and merges still each
require explicit user permission.