Skip to content

wt new <name> should resume the existing worktree instead of erroring when one is already registered #19

@ukanga

Description

@ukanga

What's wrong today

Running wt new <name> against a name that already corresponds to an existing worktree fails with a low-information error. The natural expectation is that re-running wt new demo after exiting the previous wt new demo session should drop the user back into the same worktree, the way mkdir -p is idempotent or git checkout -b falls through gracefully when the branch already exists.

Reproduction

$ wt new demo
Entering worktree: demo
(wt) $ exit

--- Exiting wt shell ---
Working tree clean.
$ wt new demo
Error: Worktree path already exists: "/.../.worktrees/demo"

The check in question is a bare directory-existence test that conflates two genuinely different states:

  1. Resumable — git already tracks the directory as a registered worktree (most common; user exited a previous wt new and is coming back).
  2. Orphan — the directory exists on disk but is not registered with git (e.g. leftover from a failed wt rm or external rm -rf).

These cases warrant different responses.

What to build

Make wt new <name> idempotent for the common case of returning to a worktree that was already created. Specifically:

  1. If git already tracks <name> as a worktree (registered in git worktree list):

    • Skip the stash/migrate step.
    • Print: Worktree '<name>' already exists, entering it. to stderr.
    • Spawn the wt shell rooted at the existing worktree path (equivalent to wt use <name>).
    • Honour --print-path: print the existing worktree path to stdout and exit, instead of spawning a shell.
  2. If the directory exists but is not a registered worktree (orphan): bail with an actionable message such as

    Error: Path '<path>' exists but is not a registered worktree.
    Run 'git worktree prune' or remove the directory before retrying.
    

    Do not auto-clean — the orphan could contain user work.

  3. The fresh-create path is unchanged.

Expected behaviour after this lands

$ wt new demo
Worktree 'demo' already exists, entering it.
(wt) $

Implementation notes

  • The split belongs in cmd_new, before the migration logic and before WorktreeManager::create_worktree is called. After the name is resolved, query WorktreeManager::worktree_exists(&name):
    • If true: load info via get_worktree_info, then either spawn the wt shell or print the path (when --print-path). Return.
    • If false: continue the existing migration + create flow.
  • The orphan check belongs in WorktreeManager::create_worktree, replacing the current bare directory-exists() bail. Cross-reference git worktree list --porcelain to confirm git does not already know about the path; if it doesn't and the path exists, emit the actionable error.
  • Skipping the migration when resuming is important: today's migrate_from_current_branch would needlessly stash uncommitted changes and switch branches even though no new worktree is being created.
  • The existing helpers WorktreeManager::worktree_exists, WorktreeManager::get_worktree_info, and wt::shell::spawn_wt_shell (already used by wt use) are reusable as-is.

Acceptance criteria

  • Running wt new <name> twice in succession (with exit between) succeeds both times and leaves the user in the existing worktree on the second run.
  • On the resume path, the message Worktree '<name>' already exists, entering it. is printed to stderr.
  • wt new <name> --print-path on a name that already exists prints the existing worktree path to stdout and exits 0 without spawning a shell.
  • When resuming, no stash is created and the current branch in the main repo is not switched (i.e. the migration step is bypassed).
  • When the directory exists but git does not track it as a worktree, wt new <name> exits non-zero with an actionable error naming the path and suggesting git worktree prune or manual removal. The directory is not modified.
  • Fresh wt new <name> against a brand-new name continues to work exactly as before (regression).
  • Integration tests cover all three states: fresh, resumable, orphan.

Out of scope

  • A --no-resume / --strict flag for create-or-fail semantics. Could be added in a follow-up if scripts need it.
  • Auto-cleaning orphan directories. Stays manual — too risky to delete without explicit user opt-in.

Blocked by

None — can start immediately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions