Worktrunk Complete Guide: Making Git Worktree and Claude Code Coexist Peacefully

发表于 2025-12-31 20:10 2588 字 13 min read

cos avatar

cos

FE / ACG / 手工 / 深色模式强迫症 / INFP / 兴趣广泛养两只猫的老宅女 / remote

Worktrunk 是一个专为并行开发场景优化的 Git Worktree CLI 工具,通过简单命令(如 `wt switch`)实现分支管理与工作目录自动创建,极大简化了多任务开发流程。它支持自动执行钩子、LLM 生成提交信息、CI 状态集成等功能,适用于 AI Agent 并行运行、快速验证想法和 PR 评审等场景,显著提升开发效率与可维护性。

This article has been machine-translated from Chinese. The translation may contain inaccuracies or awkward phrasing. If in doubt, please refer to the original Chinese version.

Preface

I’ve been using Claude Code more and more frequently lately, sometimes running three or four tasks simultaneously: one fixing a bug, one adding a new feature, one under review, and another running tests to validate new ideas. The problem is: they all need independent working directories, otherwise the code will interfere with each other.

Git’s worktree feature was designed exactly for this scenario, but the native commands are quite verbose. The traditional way to create a new worktree looks like this:

git worktree add -b feature-auth ../myproject-feature-auth
cd ../myproject-feature-auth

And removing it requires going back to the main directory:

cd ../myproject
git worktree remove ../myproject-feature-auth
git branch -d feature-auth

You have to type the branch name three times and manually cd each time. After doing this repeatedly, it starts to feel like a waste of life, which is also why I didn’t like worktree before.

Until I discovered Worktrunk, and realized that worktree management could actually be this elegant (of course there are many other tools too).

After using it for a while, it felt really great, so I’m writing a blog post to share the love.

What is Worktrunk

Simply put, Worktrunk is a CLI wrapper tool for Git Worktree, specifically optimized for scenarios where you’re running multiple AI Agents in parallel. Its core philosophy is:

Each worktree corresponds to a branch, use the branch name to manage worktrees, and paths are auto-generated.

Compare the command differences to feel the impact:

TaskWorktrunkNative Git
Switch to a worktreewt switch featcd ../repo.feat
Create worktree + start Claudewt switch -c -x claude featgit worktree add -b feat ../repo.feat && cd ../repo.feat && claude
Clean up current worktreewt removecd ../repo && git worktree remove ../repo.feat && git branch -d feat
List all worktree statuseswt listgit worktree list (only shows paths)

Essentially, it reduces the complexity of git worktree operations from “remember three parameters” to “just say a branch name”.

Why Do You Need Worktrunk?

If you only use worktree occasionally, native commands are perfectly fine. But when you need frequent parallel development (like running multiple AI Agents simultaneously) or need automated workflows (auto-installing dependencies and running tests after creating a worktree), Worktrunk can save you a lot of time and mental overhead. It essentially codifies “the proper way to use worktree” into a tool.

Installation and Configuration

Installation

macOS/Linux (Homebrew recommended):

brew install max-sixty/worktrunk/wt

Or using Cargo:

cargo install worktrunk

Shell Integration

After installation, you must run this step, otherwise wt switch cannot change directories:

wt config shell install

This adds a function to your .zshrc or .bashrc that allows the wt command to change the current shell’s working directory.

After installation, restart your terminal and run wt --version to confirm successful installation.

Core Commands Explained

wt switch - Switch/Create Worktree

wt switch | Worktrunk

This is the most commonly used command, with minimal usage:

# Switch to an existing worktree (branch name feat)
wt switch feat

# Create new worktree + branch feat based on main branch
wt switch -c feat

# Create worktree + auto-start Claude Code
wt switch -c feat -x claude

# Create new worktree + branch feat-new based on dev branch (not main) + auto-start claude
wt switch -c feat-new -b dev -x claude

Path rules: Worktrunk automatically creates worktrees in the sibling directory of the main repository, with the naming format <repo>.<branch>. For example, if the main repo is at ~/code/myproject, the worktree for branch feat will be at ~/code/myproject.feat.

Parameter explanation:

  • -c / --create: Create new worktree + branch
  • -x <cmd>: Auto-execute command after switching (e.g., claude, code, npm install)
  • -b <base>: Specify base branch (default is main)
ShortcutMeaning
^Default branch (main/master)
@Current branch/worktree
-Previous working directory (like cd -)
wt switch -                      # Back to previous
wt switch ^                      # Default branch worktree
wt switch --create fix --base=@  # Branch from current HEAD

Practical Scenarios

Suppose you’re running three parallel tasks:

# Terminal 1: Fix auth bug
wt switch -c -x claude fix-auth

# Terminal 2: Refactor API
wt switch -c -x claude refactor-api

# Terminal 3: Add new feature
wt switch -c -x claude feat-dashboard

Each Claude instance runs in an independent directory without interfering with each other.

wt list - View All Worktrees

wt list | Worktrunk

wt list

Output looks something like this:

wt list
  Branch       Status        HEAD±    main↕  Remote⇅  Commit    Age   Message
@ feature-api  +   ↕⇡     +54   -5   ↑4  ↓1   ⇡3      ec97decc  30m   Add API tests
^ main             ^⇅                         ⇡1  ⇣1  6088adb3  4d    Merge fix-auth: hardened to…
+ fix-auth|                ↑2  ↓1     |     127407de  5h    Add secure token storage

 Showing 3 worktrees, 1 with changes, 2 ahead, 1 column hidden

Much more powerful than native git worktree list, you can see a lot of information:

  • Git status: How many uncommitted/untracked files
  • CI status: If GitHub Actions is configured, it shows the latest build status

For managing multiple worktrees, this view is very practical.

wt remove - Clean Up Worktree

wt remove | Worktrunk

# Remove current worktree + branch
wt remove

# Remove specified worktree
wt remove feature-branch
wt remove old-feature another-branch

# Remove worktree but keep the branch
wt remove --no-delete-branch feature-branch

# Force delete unmerged branch
wt remove -D experimental

wt merge - Merge Workflow

wt merge | Worktrunk

This command wraps the complete “merge -> clean up” workflow:

# Merge current worktree into default branch like main
wt merge
# Merge current worktree into dev branch
wt merge dev
# Keep worktree after merge
wt merge --no-remove
# Preserve commit history (don't squash)
wt merge --no-squash
# Skip commit/merge (rebase still runs unless --no-rebase is used)
wt merge --no-commit

If a pre-merge hook is configured, tests or lint will run automatically before merging.

wt select - Interactive Selector

wt select | Worktrunk

If you can’t remember the worktree name, use the select command:

wt select

It pops up an fzf-style interface where you use arrow keys to select the worktree to switch to.

Hooks

wt hook | Worktrunk

Worktrunk supports executing commands at different stages of the worktree lifecycle, with both global user hook configuration (~/.config/worktrunk/config.toml) and project hook configuration (.config/wt.toml).

Hook Types Overview

HookTrigger TimeBlockingFail-fast
post-createAfter worktree creationYesNo
post-startAfter worktree creationNo (background)No
post-switchAfter each switchNo (background)No
pre-commitBefore commit during mergeYesYes
pre-mergeBefore merging to target branchYesYes
post-mergeAfter successful mergeYesNo
pre-removeBefore worktree deletionYesYes

Blocking mode: The main flow waits until the command finishes Fail-fast: The first failed command aborts the entire operation

Core Hooks Explained

post-create - Executes immediately after worktree creation, blocks until complete:

[post-create]
install = "npm ci"                    # Install dependencies
migrate = "npm run db:migrate"        # Database migration
env = "cp .env.example .env"          # Copy environment config

post-start - Executes in background after worktree creation, doesn’t block switching:

[post-start]
build = "npm run build"               # Build project
server = "npm run dev"                # Start dev server

Logs output to .git/wt-logs/{branch}-{source}-post-start-{name}.log

post-switch - Executes in background after every wt switch (whether creating, switching, or staying on current):

post-switch = "echo 'Switched to {{ branch }}'"

pre-commit - Executes before commit during merge, fail-fast:

[pre-commit]
format = "cargo fmt -- --check"       # Code format check
lint = "cargo clippy -- -D warnings"  # Lint check

pre-merge - Executes before merging to target branch, fail-fast:

[pre-merge]
test = "cargo test"                   # Run tests
build = "cargo build --release"       # Production build

post-merge - Executes on the target branch’s worktree after successful merge (if it exists), otherwise on the main worktree:

post-merge = "cargo install --path ."

pre-remove - Executes before worktree deletion, fail-fast:

[pre-remove]
cleanup = "rm -rf /tmp/cache/{{ branch }}"

Template Variables

Hook commands support template variables that are automatically expanded at runtime:

VariableExampleDescription
{{ repo }}my-projectRepository name
{{ branch }}feature-fooBranch name
{{ worktree }}/path/to/worktreeWorktree absolute path
{{ worktree_name }}my-project.feature-fooWorktree directory name
{{ repo_root }}/path/to/mainMain repository root path
{{ default_branch }}mainDefault branch name
{{ commit }}a1b2c3d4e5f6…HEAD full SHA
{{ short_commit }}a1b2c3dHEAD short SHA
{{ remote }}originPrimary remote name
{{ remote_url }}git@github.com
/repo.git
Remote URL
{{ upstream }}origin/featureUpstream tracking branch
{{ target }}mainTarget branch (merge hooks only)

Security Mechanism

Project hooks (.config/wt.toml) require user approval on first run:

▲ repo needs approval to execute 3 commands:
○ post-create install:
  echo 'Installing dependencies...'
❯ Allow and remember? [y/N]
  • Approval records are saved in user config
  • Command changes require re-approval
  • --yes skips prompts (suitable for CI)
  • --no-verify completely skips hooks

Managing approval records:

wt hook approvals add      # Pre-approve all commands for current project
wt hook approvals clear    # Clear approvals for current project
wt hook approvals clear --global  # Clear global approvals

User-Level Hooks

Hooks configured in ~/.config/worktrunk/config.toml apply to all repositories and don’t require approval:

DimensionProject HookUser Hook
Config Location.config/wt.toml~/.config/worktrunk/config.toml
ScopeSingle repositoryAll repositories
Requires ApprovalYesNo
Execution OrderRuns laterRuns first

Looking at it, I think this hook is very important — it can reuse the main worktree’s cache:

[post-create]
# Symlink node_modules to avoid duplicate installs
cache = "ln -sf {{ repo_root }}/node_modules node_modules"
env = "cp {{ repo_root }}/.env.local .env"

Running Hooks Independently

Besides automatic triggers, you can also manually run hooks for testing or retrying:

wt hook pre-merge           # Run pre-merge hooks
wt hook pre-merge --yes     # Skip approval prompt (suitable for CI)
wt hook show                # View all configured hooks

Use cases: Hook development testing, CI pipelines, retrying after failure, etc.

Claude Code Plugin Status Monitoring

claude plugin marketplace add max-sixty/worktrunk
claude plugin install worktrunk@worktrunk

Manually Setting Status Markers

Besides auto-tracking Claude status, you can also manually set any marker on a worktree for other workflows:

# Set marker on current branch
wt config state marker set "🚧"

# Set marker on specified branch
wt config state marker set "✅" --branch feature

# Directly manipulate Git Config (advanced usage)
git config worktrunk.state.feature.marker '{"marker":"💬","set_at":0}'

Statusline

wt list statusline --claude-code outputs a compact status line for Claude Code’s statusline display:

~/w/myproject.feature-auth !🤖 @+42 -8 ↑3 ⇡1 ● | Opus

LLM Commit Messages - Auto-generating Commit Messages

LLM Commit Messages | Worktrunk

Worktrunk can call LLMs to automatically generate commit messages, integrated into wt merge, wt step commit, wt step squash, and other commands.

It works without configuration — by default, it generates simple messages based on file names, but configuring an LLM can produce more meaningful commit messages.

Installation and Configuration

1. Install the llm tool

Uses llm:

uv tool install -U llm
# Or directly via brew
brew install llm

2. Configure API Key

Choose your preferred LLM provider:

# Use Claude (recommended)
llm install llm-anthropic
llm keys set anthropic
# Enter your Anthropic API Key

# Or use OpenAI
llm keys set openai
# Enter your OpenAI API Key

3. Configure Worktrunk

Create the config file (if not already present):

wt config create

Edit ~/.config/worktrunk/config.toml, add:

[commit-generation]
command = "llm"
args = ["-m", "claude-3-5-haiku-latest"]  # Or gpt-4o-mini

Usage Scenarios

After configuration, the following commands will auto-generate commit messages:

Scenario 1: Generate squash commit when merging branches

$ wt merge
 Squashing 3 commits into a single commit (5 files, +48)...
 Generating squash commit message...
   feat(auth): Implement JWT authentication system

   - Add JWT token generation and validation
   - Implement refresh token mechanism
   - Add middleware for protected routes

Scenario 2: Commit current changes

$ wt step commit
 Generating commit message...
   fix(api): Handle null response in user endpoint

Scenario 3: Squash multiple commits into one

$ wt step squash
 Squashing 5 commits...
 Generating squash commit message...
   refactor(ui): Modernize component architecture

Using Other AI Tools

Besides llm, any tool that can read prompts from stdin and output text works:

# Using aichat
[commit-generation]
command = "aichat"
args = ["-m", "claude:claude-3-5-haiku-latest"]

# Using custom script
[commit-generation]
command = "./scripts/generate-commit.sh"

Fallback Behavior

Without LLM configured, Worktrunk generates simple file-name-based messages:

Changes to auth.rs & config.rs

CI Status Integration

If you use GitHub Actions, Worktrunk can show build status in wt list:

# Configure GitHub Token
wt config github.token "ghp_xxxxxxxxxxxx"

# After that, wt list will show CI status
wt list

Practical Workflows

Running Multiple Claude Code Instances in Parallel

Suppose you’re using Zellij or Tmux to manage multiple terminal windows:

# Pane 1: Fix bug
wt switch -c -x claude fix-login-bug

# Pane 2: Refactor
wt switch -c -x claude refactor-utils

# Pane 3: New feature
wt switch -c -x claude feat-export

Each Claude works in an independent directory. You can switch back to the main directory at any time to see the global status:

wt switch main
wt list

Quickly Verifying Ideas

Sometimes you want to quickly verify an idea without polluting the current branch:

# Create temporary worktree
wt switch -c -x code experiment

# Done verifying, just delete it
wt remove

Since worktrees are independent directories, deleting them won’t affect other branches.

Running Code During PR Review

When a PR comes in and you want to run the code locally:

# Create worktree based on PR branch
wt switch -c review-pr-123 -b origin/pr-123

# Install deps, run tests
npm install
npm run test

# Done reviewing, clean up
wt remove

References

喜欢的话,留下你的评论吧~

© 2020 - 2026 cos @cosine
Powered by theme astro-koharu · Inspired by Shoka