Files
Maximilian Roos a27cbd42be feat(skill): create the worktree by name, fall back to a path (#3636)
Closes #3555. `/wt-switch-create` now creates through
`EnterWorktree({name})` for the common invocation, which asks the user
to confirm nothing, and falls back to `wt` plus a path entry for the
cases that route can't serve.

The confirmation Claude Code 2.1.206 added fires on a call that passes
`path` and targets a worktree outside `.claude/worktrees/`. `{name}`
passes no `path`, so it never fires. With the plugin installed, `{name}`
runs worktrunk's own `WorktreeCreate` hook (`wt switch --create`), so
the worktree is the one `wt` would have made, and it shows up in `wt
list`.

## Routes

- **New branch, this repo** → `EnterWorktree({name})`. No confirmation.
- **Anything else** → `wt -C <repo> switch --create … --format=json`,
then `EnterWorktree({path})`, keeping the outcome handling below.

Three things `{name}` cannot do, each announcing its own fallback in the
error it returns, so the skill tries the cheap call and reads the result
rather than pre-checking:

| Case | What `{name}` returns |
|---|---|
| Repo argument given | no `-C` equivalent; skipped before the call |
| Branch already exists | the hook exits nonzero with `✗ Branch <branch>
already exists` |
| Session already entered a worktree | `Already in a worktree session.
Pass `path` to switch into another existing worktree` |

## The tradeoff, taken deliberately

A `{name}` worktree the session never touched is removed when the
session ends, branch included, through the plugin's `WorktreeRemove`
hook (`wt remove`). Verified end to end: create, `/exit`, and neither
the worktree nor the branch remains; one untracked file is enough to
keep both. That is wanted at this scale, since a research task that
wrote nothing leaves nothing to prune, but it does mean "the worktree
persists" was no longer true of every route. Cleanup and the rationale
now say which worktrees persist; the user docs stay out of the
mechanics.

## A bug fixed along the way

A user declining the path-entry confirmation returns an ordinary
permission denial, which the skill's single rejection branch caught, and
that branch's documented recovery is to `cd` into the worktree and work
there. So a declined entry became a worktree entered anyway. Three
wordings that asked the agent to classify the denial text failed
context-blind probes in turn: branches labeled by who refused, "the
denial reports a decision", and that plus the verbatim denial string
quoted as an example.

Entry outcomes now split structurally instead. The tool's own `Cannot
enter` errors key the reachability test; a denial of the call stops and
asks by default, with one exception: a session with no user to ask (its
denial says it couldn't prompt) takes the recovery, since nothing was
decided. A recovery that follows a denial invites the model to route any
denial into it, so the denial branch leads with stopping. Context-blind
agents route both denial directions and the four invocation shapes above
through their intended routes.

## Verification

All of it re-run live against Claude Code 2.1.220 in scratch repos,
since the analysis in #3555 rested on properties pinned to 2.1.173/177
and could not be checked from CI. Two claims died that way: an earlier
draft credited the exit removal to the harness when it is worktrunk's
own hook, and asserted that a `permissions.deny` rule produces a
classifiable denial, when it removes the tool from the session entirely.

Also recorded in the rationale: the confirmation offers no always-allow
and `permissions.allow` cannot suppress it, it fires in `default`,
`acceptEdits`, and `auto` while `bypassPermissions` allows silently, and
a session that cannot prompt denies without asking.

> _This was written by Claude Code on behalf of Maximilian_

---------

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:07:36 -07:00
..