wt is a small local Bash wrapper around git worktree that makes creating, navigating, merging, and removing linked worktrees fast and safe.
It works with any Git repository. Node.js projects get additional conveniences (lockfile-based package manager detection and automatic dependency installation), but those features are skipped gracefully when no supported lockfile or packageManager field is found. You can use wt on a Go, Python, Rust, or plain-text repository with no changes.
Core design principles:
- uses a sibling worktree root at
<repo>__worktrees/ - keeps the Git branch name separate from the filesystem handle
- copies local bootstrap files and directories from the repo root and Git-managed directory tree only when the source exists and the target is missing
- installs dependencies only when it can confidently detect the package manager
- never starts a dev server or pushes to a remote
Add this repository's bin directory to your PATH:
export PATH="$(pwd)/bin:$PATH"Source the shell helper to get cd behavior in Bash or zsh:
source "$(pwd)/shell/wt.bash"With the sourced wrapper:
wt cd <name>changes into the linked worktree in your current shellwt new <branch>andwt new(interactive mode) also move you into the new worktreewt rmwith no target hops back to the primary worktree before removing the current linked worktreewt mergemoves you back to the primary worktree after a successful mergewt synckeeps you in the current linked worktree after syncing from primary
Here, "primary" means the repository-root checkout managed by git worktree, using whatever branch is currently checked out there.
The wrapper resolves the bundled bin/wt directly, so it does not depend on command wt lookup. The wt --help output describes the binary itself, while the sourced wrapper adds shell-level cd behavior on top.
- Required:
git,bash - Required for AI features: OpenCode (
opencodeonPATH) - Required for portless integration:
portlessonPATH,python3 - Optional: a supported Node.js package manager (
pnpm,npm, orbun) for automatic dependency installation
Create a linked worktree for a branch.
During creation, wt new also copies local bootstrap entries from the primary checkout into the new worktree when they exist and the destination path is missing. This covers the standard .env file plus any file or directory whose basename matches *.local or *.local.* at the repo root or inside directories implied by tracked files. For example, a local-only skill at .agents/skills/foo-skill.local/ is copied when .agents/skills/ is part of the tracked project tree.
After creation, wt new prints a grouped human-readable summary with Worktree, Created worktree, and Bootstrap sections. The raw git worktree add messages are folded into that summary and streamed immediately instead of appearing only after later bootstrap steps finish. In an interactive terminal it also adds ANSI color, indentation, and copied-entry details to feel more like a CLI tool, while non-interactive output stays plain text. The existing parseable key: value lines such as worktree_path: ... remain in place so the shell wrapper can still extract the new worktree path.
wt status, wt diff, wt prune, wt init, wt b, wt merge, wt sync, and wt rm use the same section-oriented output style so the CLI feels consistent across the main workflow commands. When these commands surface subprocess output, interactive terminals render that subprocess output dimmed so the higher-level wt summary stays visually primary.
wt new feature/testWhen called without a branch argument in an interactive terminal, wt new enters an AI-assisted interactive flow:
- Prompts you to describe what you want to do in the new worktree
- Uses OpenCode to suggest a branch name based on your description
- Lets you accept or edit the suggested name
- Asks whether to launch OpenCode in the new worktree with your original description as the prompt
wt new
# What do you want to do in this worktree? add dark mode support
# Branch name [feat/dark-mode]: <Enter to accept, or type a different name>
# Launch opencode with this prompt? (y/n)Print the absolute path for a linked worktree. With the shell wrapper sourced, this also changes your working directory.
wt cd feature/testPrint the absolute path for a linked worktree (same resolution as wt cd, without the shell wrapper's cd behavior).
wt open feature/testList the primary checkout and all linked worktrees with their branch, handle, type, and state.
wt lsShow the current or requested worktree, including its state and relationship to the branch currently checked out in the primary worktree.
- For linked worktrees,
wt statusshows ahead/behind counts plussync_statusandmerge_statusso you can see whetherwt syncorwt mergewould fast-forward, require a merge, or be blocked. - For the primary checkout,
wt statusshows how many linked worktrees exist and how many stale entries are currently missing or prunable.
wt status
wt status feature/testShow the committed changes for the current or requested linked worktree against the current primary branch, using the merge base as the comparison point. wt diff lists target-only commits first and then prints the patch.
When run from the primary checkout, pass a branch-or-handle explicitly.
wt diff
wt diff feature/testwt diff refuses dirty worktrees because it compares committed branch state only.
Merge the current linked worktree's branch into the branch currently checked out in the primary worktree, then clean up (remove the worktree and delete the branch).
wt mergeThe merge strategy is:
- If the primary branch has no new commits, fast-forward directly.
- Otherwise, merge the primary branch into the feature branch first (reverse merge) to keep the feature branch safe from conflicts, then fast-forward the primary branch.
- If the reverse merge produces conflicts, OpenCode (with the Maat agent) is launched to resolve them automatically. If AI resolution fails, the merge is aborted cleanly and the worktree is left intact.
Merge the branch currently checked out in the primary worktree into the current linked worktree, keeping the worktree and branch intact.
wt syncThe sync strategy is:
- If the current worktree branch can be fast-forwarded to the primary branch, do that directly.
- Otherwise, merge the primary branch into the current worktree branch.
- If that merge produces conflicts, OpenCode (with the Maat agent) is launched to resolve them automatically. If AI resolution fails, the merge is aborted cleanly and the worktree is left intact.
Remove a linked worktree conservatively.
wt rm # remove current worktree (shell wrapper only)
wt rm feature/test # remove by branch or handle
wt rm --force # remove even if dirty
wt rm --force feature/testClean up stale linked worktree metadata using git worktree prune --expire now.
- Targets linked worktrees that are missing on disk or already marked prunable
- Leaves branch refs alone
- Skips locked entries conservatively
- Supports
--dry-runto preview the cleanup without changing Git metadata
wt prune
wt prune --dry-runGenerate or update .vscode/launch.json for the current worktree with a Chrome DevTools attach configuration derived from the portless URL.
wt initOpen the current or requested worktree in a Chrome-compatible debug browser, deriving the URL from portless. Reuses an existing debug browser when possible.
wt b # current worktree
wt b feature/test # specific worktreewt refuses or avoids several behaviors on purpose:
- it must be run from inside the repository you want to manage
- it will not delete the primary worktree
- it refuses locked worktrees in v1
- it refuses dirty worktrees unless you pass
--force - when a removed linked worktree is clean, it also tries
git branch -d; with--force, it usesgit branch -D - it does not symlink
node_modulesor share generated framework directories wt statusreports linked worktree state relative to whatever branch is currently checked out in the primary checkoutwt diffcompares committed target changes against the current primary branch and refuses dirty worktreeswt mergerefuses dirty worktrees and branches with no commits aheadwt mergemerges the primary branch into the feature branch first to keep it safe from conflictswt mergeuses AI to resolve conflicts, and aborts cleanly if resolution failswt mergenever pushes to a remotewt syncrefuses dirty worktrees and refuses to run when the primary branch has no commits ahead of the current linked branchwt syncmerges the primary branch into the current linked worktree and keeps that worktree in placewt syncuses AI to resolve conflicts, and aborts cleanly if resolution failswt pruneonly removes stale linked worktree metadata, skips locked entries, and leaves branch refs intact- real
cdbehavior requires sourcingshell/wt.bash, because a subprocess cannot change the parent shell directory
Detection is conservative and lockfile-first:
| Detected file | Install command |
|---|---|
pnpm-lock.yaml |
pnpm install --prefer-offline |
package-lock.json |
npm install --prefer-offline |
bun.lock or bun.lockb |
bun install |
package.json packageManager field |
corresponding manager |
If nothing is detected, dependency installation is skipped with an explicit message. This is the expected behavior for non-Node.js repositories.
| Variable | Default | Description |
|---|---|---|
WT_BRANCH_NAME_MODEL |
opencode-go/kimi-k2.5 |
OpenCode model used for AI branch name suggestion in wt new interactive mode |
WT_MERGE_MODEL |
opencode-go/glm-5 |
OpenCode model used for AI merge conflict resolution |
WT_NEW_WORKTREE_AGENT |
Build |
OpenCode agent used when wt new launches an interactive session in the new worktree |
WT_DEBUG_PORT |
9222 |
Chrome remote debugging port for wt b and wt init |
WT_DEBUG_USER_DATA_DIR |
~/.vscode/chrome |
Chrome user data directory for the debug browser |
WT_CHROME_BIN |
auto-detected | Path to a Chrome-compatible browser binary |
wt does not depend on portless, and it does not start portless or any app process.
When wt new creates a worktree in a repository whose package.json scripts.dev is a portless command, wt automatically runs wt init to set up the debug browser configuration. The portless URL for the linked worktree is derived from the app name, so each worktree gets a distinct local URL when you start the app yourself.
wt b and wt init both require portless to be available on PATH.
Run the smoke test suite:
bash tests/smoke.shTests use temporary repositories and fake binaries for external dependencies (OpenCode, portless, Chrome), so they run without network access or real browser instances.