Worktrunk
Worktrunk is a CLI for Git worktree management, designed for parallel AI agent
workflows. Git worktrees give each agent an isolated branch and directory;
Worktrunk adds branch-based navigation, unified status, and lifecycle hooks. The
goal is to make spinning up a new AI "developer" for a task feel as routine as
git switch.
December 2025 Project Status
I've been using Worktrunk as my daily driver, and am releasing it as Open Source this week. It's built with love (there's no slop!). If social proof is helpful: I also created PRQL (10k stars) and am a maintainer of Xarray (4k stars), Insta, & Numbagg.
I'd recommend:
- starting with Worktrunk as a simpler & better
git worktree: create / navigate / list / clean up git worktrees with ease - later using the more advanced features if you find they resonate: there's lots for the more ambitious, such as LLM commit messages, or local merging of worktrees gated on CI-like checks, or fzf-like selector + preview. And QoL features, such as listing the CI status & the Claude Code status for all branches, or a great Claude Code statusline. But they're not required to get value from the tool.
Demo

Quick Start
1. Install
Homebrew (macOS):
Cargo:
2. Create a worktree
This creates ../repo.fix-auth on branch fix-auth.
3. Switch between worktrees
4. List worktrees
--full adds CI status and conflicts. --branches includes all branches.
5. Clean up
Say we merged via CI, our changes are on main, and we're finished with the worktree:
& )
Why git worktrees?
We have a few options for working with multiple agents:
- one working tree with many branches — agents step on each other, can't use git for staging & committing
- multiple clones — slow to set up, drift out of sync
- git worktrees — multiple directories backed by a single
.gitdirectory
So we use git worktrees! But then...
Why Worktrunk?
Git's built-in worktree commands require remembering worktrees' locations, and
composing git & cd commands together. In contrast, Worktrunk bundles creation,
navigation, status, and cleanup into simple commands. A few examples:
...and check out examples below for more advanced workflows.
Advanced
Many Worktrunk users will just use the commands above. For more:
LLM commit messages
Worktrunk can invoke external commands to generate commit messages. llm from @simonw is recommended.
Add to user config (~/.config/worktrunk/config.toml):
[]
= "llm"
= ["-m", "claude-haiku-4-5-20251001"]
wt merge generates commit messages automatically or wt step commit runs just the commit step.
For custom prompt templates: wt config --help.
Project hooks
Automate setup and validation at worktree lifecycle events:
| Hook | When | Example |
|---|---|---|
| post-create | After worktree created | cp -r .cache, ln -s |
| post-start | After worktree created (background) | npm install, cargo build |
| pre-commit | Before creating any commit | pre-commit run |
| pre-merge | After squash, before push | cargo test, pytest |
| post-merge | After successful merge | cargo install --path . |
Project commands require approval on first run; use --force to skip prompts
or --no-verify to skip hooks entirely. Configure in .config/wt.toml:
# Install dependencies, build setup
[]
= "uv sync"
# Dev servers, file watchers (runs in background)
[]
= "uv run dev"
# Tests and lints before merging (blocks on failure)
[]
= "uv run ruff check"
= "uv run pytest"
Example output:
Local merging with wt merge
wt merge handles the full merge workflow: stage, commit, squash, rebase,
merge, cleanup. Includes LLM commit messages,
project hooks, and config/flags
for skipping steps.
)
)
)
)
; ; ; ; ;
)
)
)
|
|
|
)
)
& )
)
)
Claude Code Status Tracking
The Worktrunk plugin adds Claude Code session tracking to wt list:
🤖— Claude is working💬— Claude is waiting for input
Install the plugin:
Set status markers manually for any workflow:
Interactive Worktree Picker
wt select opens a fzf-like fuzzy-search worktree picker with diff preview. Unix only.
Preview tabs (toggle with 1/2/3):
- Tab 1: Working tree changes (uncommitted)
- Tab 2: Commit history (commits not on main highlighted)
- Tab 3: Branch diff (changes ahead of main)
Statusline Integration
wt list statusline outputs a single-line status for shell prompts, starship,
or editor integrations[^1].
[^1]: Currently this grabs CI status, so is too slow to use in synchronous contexts. If a faster version would be helpful, please add an Issue.
Claude Code (--claude-code): Reads workspace context from stdin, outputs
directory, branch status, and model.
~/w/myproject.feature-auth !🤖 ±+42 -8 ↑3 ⇡1 ● | Opus
Add to ~/.claude/settings.json:
Tips & patterns
Alias for new worktree + agent:
Eliminate cold starts — post-create hooks install deps and copy caches.
See .config/wt.toml for an example using copy-on-write.
Local CI gate — pre-merge hooks run before merging. Failures abort the
merge.
Track agent status — Custom emoji markers show agent state in wt list.
Claude Code hooks can set these automatically. See Claude Code Status
Tracking.
Monitor CI across branches — wt list --full --branches shows PR/CI status
for all branches, including those without worktrees. CI column links to PR pages
in terminals with hyperlink support.
JSON API — wt list --format=json for dashboards, statuslines, scripts.
Task runners — Reference Taskfile/Justfile/Makefile in hooks:
[]
= "task install"
[]
= "just test lint"
Shortcuts — ^ = default branch, @ = current branch, - = previous
worktree. Example: wt switch --create hotfix --base=@ branches from current
HEAD.
Commands Reference
wt switch — Switch to a worktree
Usage: wt switch [OPTIONS] <BRANCH>
Arguments:
<BRANCH>
Branch or worktree name
Shortcuts: '^' (main), '-' (previous), '@' (current)
Options:
-c, --create
Create a new branch
-b, --base <BASE>
Base branch
Defaults to default branch.
-x, --execute <EXECUTE>
Command to run after switch
-f, --force
Skip approval prompts
--no-verify
Skip all project hooks
-h, --help
Print help (see a summary with '-h')
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
-v, --verbose
Show commands and debug info
Two distinct operations:
- **Switch to existing worktree** — Changes directory, nothing else
- **Create new worktree** (`--create`) — Creates branch and worktree, runs [hooks](/hooks/)
Examples
For interactive selection, use wt select.
Shortcuts
| Symbol | Meaning |
|---|---|
- |
Previous worktree |
@ |
Current branch's worktree |
^ |
Default branch worktree |
Path-First Lookup
Arguments resolve by checking the filesystem before git branches:
- Compute expected path from argument (using configured path template)
- If worktree exists at that path, switch to it
- Otherwise, treat argument as branch name
Edge case: If repo.foo/ exists but tracks branch bar:
wt switch foo→ switches torepo.foo/(thebarworktree)wt switch bar→ also works (branch lookup finds same worktree)
Creating Worktrees
With --create, worktrunk:
- Creates branch from
--base(defaults to default branch) - Creates worktree at configured path
- Runs post-create hooks (blocking)
- Switches to new directory
- Spawns post-start hooks (background)
See Also
- wt select — Interactive worktree selection
- wt list — View all worktrees
- wt remove — Delete worktrees when done
- wt merge — Integrate changes back to main
wt merge — Merge worktree into target branch
Usage: wt merge [OPTIONS] [TARGET]
Arguments:
[TARGET]
Target branch
Defaults to default branch.
Options:
--no-squash
Skip commit squashing
--no-commit
Skip commit, squash, and rebase
--no-remove
Keep worktree after merge
--no-verify
Skip all project hooks
-f, --force
Skip approval prompts
--stage <STAGE>
What to stage before committing [default: all]
Possible values:
- all: Stage everything: untracked files + unstaged tracked changes
- tracked: Stage tracked changes only (like git add -u)
- none: Stage nothing, commit only what's already in the index
-h, --help
Print help (see a summary with '-h')
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
-v, --verbose
Show commands and debug info
Merge the current branch into the target branch and clean up. Handles the full workflow: commit uncommitted changes, squash commits, rebase, run hooks, push to target, and remove the worktree.
When already on the target branch or in the main worktree, the worktree is preserved automatically.
Examples
Basic merge to main:
Keep the worktree after merging:
Preserve commit history (no squash):
Skip git operations, only run hooks and push:
Pipeline
wt merge runs these steps:
-
Commit — Stages and commits uncommitted changes. Commit messages are LLM-generated. Use
--stageto control what gets staged:all(default),tracked, ornone. -
Squash — Combines all commits into one (like GitHub's "Squash and merge"). Skip with
--no-squashto preserve individual commits. A backup ref is saved torefs/wt-backup/<branch>. -
Rebase — Rebases onto the target branch. Conflicts abort immediately.
-
Pre-merge hooks — Project commands run after rebase, before push. Failures abort. See Hooks.
-
Push — Fast-forward push to the target branch. Non-fast-forward pushes are rejected.
-
Cleanup — Removes the worktree and branch. Use
--no-removeto keep the worktree. -
Post-merge hooks — Project commands run after cleanup. Failures are logged but don't abort.
Use --no-commit to skip steps 1-3 and only run hooks and push. Requires a clean working tree and --no-remove.
See Also
- wt step — Run individual merge steps (commit, squash, rebase, push)
- wt remove — Remove worktrees without merging
- wt switch — Navigate to other worktrees
wt remove — Remove worktree and branch
Usage: wt remove [OPTIONS] [WORKTREES]...
Arguments:
[WORKTREES]...
Worktree or branch (@ for current)
Options:
--no-delete-branch
Keep branch after removal
-D, --force-delete
Delete unmerged branches
--no-background
Run removal in foreground
-h, --help
Print help (see a summary with '-h')
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
-v, --verbose
Show commands and debug info
Removes worktrees and their branches. Without arguments, removes the current worktree and returns to the main worktree.
Examples
Remove current worktree:
Remove specific worktrees:
Keep the branch:
Force-delete an unmerged branch:
When Branches Are Deleted
Branches delete automatically when their content is already in the target branch (typically main). This works with squash-merge and rebase workflows where commit history differs but file changes match.
Use -D to force-delete unmerged branches. Use --no-delete-branch to keep the branch.
Background Removal
Removal runs in the background by default (returns immediately). Logs are written to .git/wt-logs/{branch}-remove.log. Use --no-background to run in the foreground.
Path-First Lookup
Arguments resolve by checking the expected path first, then falling back to branch name:
- Compute expected path from argument (using configured path template)
- If a worktree exists there, remove it (regardless of branch name)
- Otherwise, treat argument as a branch name
If repo.foo/ exists on branch bar, both wt remove foo and wt remove bar remove the same worktree.
Shortcuts: @ (current), - (previous), ^ (main worktree)
See Also
wt list — List worktrees and optionally branches
Usage: wt list [OPTIONS]
wt list <COMMAND>
Commands:
statusline Single-line status for shell prompts
Options:
--format <FORMAT>
Output format (table, json)
[default: table]
--branches
Include branches without worktrees
--remotes
Include remote branches
--full
Show CI, conflicts, diffs
--progressive
Show fast info immediately, update with slow info
Displays local data (branches, paths, status) first, then updates with remote data (CI, upstream) as it arrives. Auto-enabled for TTY.
-h, --help
Print help (see a summary with '-h')
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
-v, --verbose
Show commands and debug info
Show all worktrees with their status. The table includes uncommitted changes, divergence from main and remote, and optional CI status.
Examples
List all worktrees:
Include CI status and conflict detection:
Include branches that don't have worktrees:
Output as JSON for scripting:
Status Symbols
Symbols appear in the Status column in this order:
Working tree state:
+— Staged files!— Modified files (unstaged)?— Untracked files✖— Merge conflicts↻— Rebase in progress⋈— Merge in progress
Branch state:
⊘— Would conflict if merged to main (--fullonly)≡— Matches main (identical contents)_— No commits (empty branch)
Divergence from main:
↑— Ahead of main↓— Behind main↕— Diverged from main
Remote tracking:
⇡— Ahead of remote⇣— Behind remote⇅— Diverged from remote
Other:
⎇— Branch without worktree⌫— Prunable (directory missing)⊠— Locked worktree
Rows are dimmed when the branch has no marginal contribution (≡ matches main or _ no commits).
Columns
| Column | Shows |
|---|---|
| Branch | Branch name |
| Status | Compact symbols (see above) |
| HEAD± | Uncommitted changes: +added -deleted lines |
| main↕ | Commits ahead/behind main |
| main…± | Line diffs in commits ahead of main (--full) |
| Path | Worktree directory |
| Remote⇅ | Commits ahead/behind tracking branch |
| CI | Pipeline status (--full) |
| Commit | Short hash (8 chars) |
| Age | Time since last commit |
| Message | Last commit message (truncated) |
The CI column shows GitHub/GitLab pipeline status:
●green — All checks passed●blue — Checks running●red — Checks failed●yellow — Merge conflicts with base●gray — No checks configured- blank — No PR/MR found
- dimmed — Stale (unpushed local changes)
JSON Output
Query structured data with --format=json:
# Worktrees with conflicts
|
# Uncommitted changes
|
# Current worktree
|
# Branches ahead of main
|
Status fields:
working_tree:{untracked, modified, staged, renamed, deleted}branch_state:""|"Conflicts"|"MergeTreeConflicts"|"MatchesMain"|"NoCommits"git_operation:""|"Rebase"|"Merge"main_divergence:""|"Ahead"|"Behind"|"Diverged"upstream_divergence:""|"Ahead"|"Behind"|"Diverged"
Position fields:
is_main— Main worktreeis_current— Current directoryis_previous— Previous worktree from wt switch
See Also
- wt select — Interactive worktree picker with live preview
wt config — Manage configuration and shell integration
Usage: wt config [OPTIONS] <COMMAND>
Commands:
shell Shell integration setup
create Create user configuration file
show Show configuration files & locations
cache Manage caches (CI status, default branch)
var Get or set runtime variables (stored in git config)
approvals Manage command approvals
Options:
-h, --help
Print help (see a summary with '-h')
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
-v, --verbose
Show commands and debug info
Manages configuration, shell integration, and runtime settings. The command provides subcommands for setup, inspection, and cache management.
Examples
Install shell integration (required for directory switching):
Create user config file with documented examples:
Show current configuration and file locations:
Shell Integration
Shell integration allows Worktrunk to change the shell's working directory after wt switch. Without it, commands run in a subprocess and directory changes don't persist.
The wt config shell install command adds integration to the shell's config file. Manual installation:
# For bash: add to ~/.bashrc
# For zsh: add to ~/.zshrc
# For fish: add to ~/.config/fish/config.fish
|
Configuration Files
User config — ~/.config/worktrunk/config.toml (or $WORKTRUNK_CONFIG_PATH):
Personal settings like LLM commit generation, path templates, and default behaviors. The wt config create command generates a file with documented examples.
Project config — .config/wt.toml in repository root:
Project-specific hooks: post-create, post-start, pre-commit, pre-merge, post-merge. See Hooks for details.
LLM Commit Messages
Worktrunk can generate commit messages using an LLM. Enable in user config:
[]
= "llm"
See LLM Commits for installation, provider setup, and customization.
wt step — Run individual workflow operations
Usage: wt step [OPTIONS] <COMMAND>
Commands:
commit Commit changes with LLM commit message
squash Squash commits with LLM commit message
push Push changes to local target branch
rebase Rebase onto target
post-create Run post-create hook
post-start Run post-start hook
pre-commit Run pre-commit hook
pre-merge Run pre-merge hook
post-merge Run post-merge hook
Options:
-h, --help
Print help (see a summary with '-h')
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
-v, --verbose
Show commands and debug info
Run individual workflow operations: commits, squashes, rebases, pushes, and [hooks](/hooks/).
Examples
Commit with LLM-generated message:
Run pre-merge hooks in CI:
Manual merge workflow with review between steps:
# Review the squashed commit
Operations
Git operations:
commit— Stage and commit with LLM-generated messagesquash— Squash all branch commits into one with LLM-generated messagerebase— Rebase onto target branchpush— Push to target branch (default: main)
Hooks — run project commands defined in .config/wt.toml:
post-create— After worktree creationpost-start— After switching to a worktreepre-commit— Before committingpre-merge— Before pushing to targetpost-merge— After merge cleanup
See Also
- wt merge — Runs commit → squash → rebase → hooks → push → cleanup automatically
FAQ
Worktrunk executes commands in three contexts:
- Project hooks (project config:
.config/wt.toml) - Automation for worktree lifecycle - LLM commands (user config:
~/.config/worktrunk/config.toml) - Commit message generation - --execute flag - Commands provided explicitly
Commands from project hooks and LLM configuration require approval on first run. Approved commands are saved to user config under the project's configuration. If a command changes, Worktrunk requires new approval.
Example approval prompt:
🟡 repo needs approval to execute 3 commands:
⚪ post-create install:
echo 'Installing dependencies...'
⚪ post-create build:
echo 'Building project...'
⚪ post-create test:
echo 'Running tests...'
💡 Allow and remember? [y/N]
Use --force to bypass prompts (useful for CI/automation).
vs. Branch Switching
Branch switching uses one directory, so only one agent can work at a time. Worktrees give each agent its own directory.
vs. Plain git worktree
Git's built-in worktree commands work but require manual lifecycle management:
# Plain git worktree workflow
# ...work, commit, push...
Worktrunk automates the full lifecycle:
# ...work...
What git worktree doesn't provide:
- Consistent directory naming and cleanup validation
- Project-specific automation (install dependencies, start services)
- Unified status across all worktrees (commits, CI, conflicts, changes)
Worktrunk adds path management, lifecycle hooks, and wt list --full for viewing all worktrees—branches, uncommitted changes, commits ahead/behind, CI status, and conflicts—in a single view.
vs. git-machete / git-town
Different scopes:
- git-machete: Branch stack management in a single directory
- git-town: Git workflow automation in a single directory
- worktrunk: Multi-worktree management with hooks and status aggregation
These tools can be used together—run git-machete or git-town inside individual worktrees.
vs. Git TUIs (lazygit, gh-dash, etc.)
Git TUIs operate on a single repository. Worktrunk manages multiple worktrees, runs automation hooks, and aggregates status across branches. TUIs work inside each worktree directory.
Errors related to tree-sitter or C compilation (C99 mode, le16toh undefined)
can be avoided by installing without syntax highlighting:
This disables bash syntax highlighting in command output but keeps all core functionality. The syntax highlighting feature requires C99 compiler support and can fail on older systems or minimal Docker images.
- Star the repo
- Try it out and open an issue with feedback
- Send to a friend
- Post about it — X · Reddit · LinkedIn
Thanks in advance!
Running Tests
Quick tests (no external dependencies):
Full integration tests (requires bash, zsh, fish):
Dependencies for shell integration tests:
- bash, zsh, fish shells
- Quick setup:
./dev/setup-claude-code-web.sh(installs shells on Linux)
Releases
Use cargo-release to publish new versions:
# Bump version, update Cargo.lock, commit, tag, and push
This updates Cargo.toml and Cargo.lock, creates a commit and tag, then pushes to GitHub. The tag push triggers GitHub Actions to build binaries, create the release, and publish to crates.io.
Run without --execute to preview changes first.
Updating Homebrew Formula
After cargo release completes and the GitHub release is created, update the homebrew-worktrunk tap:
This script fetches the new tarball, computes the SHA256, updates the formula, and pushes to homebrew-worktrunk.