Skip to content
Back to skills

Worktrunk

ASecurity

Sets up and drives Worktrunk (the `wt` CLI) so several branches, each in its own git worktree, can be worked on at once by people or AI coding agents. Use when a user says "run three agents in parallel on this repo", "give each task its own worktree", "set up worktrunk", "wt switch does not change directory", "add a hook that installs dependencies in new worktrees", "merge this worktree back and clean it up", or asks why a hook wants approval. Covers installation, the switch / list / merge / ...

  • 142 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 6, 2026
toolspythonrustgoshellbashnodegitapidatabase

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill worktrunk --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Worktrunk?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Worktrunk
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-worktrunk/badge)](https://www.skillsdirectory.com/skills/terminalskills-worktrunk)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: worktrunk
description: >-
  Sets up and drives Worktrunk (the `wt` CLI) so several branches, each in its own git worktree,
  can be worked on at once by people or AI coding agents. Use when a user says "run three agents
  in parallel on this repo", "give each task its own worktree", "set up worktrunk", "wt switch
  does not change directory", "add a hook that installs dependencies in new worktrees", "merge
  this worktree back and clean it up", or asks why a hook wants approval. Covers installation,
  the switch / list / merge / remove loop, the two config files and their real keys, hooks and
  their approval step, launching agents with -x, JSON output for scripts, and safe cleanup.
license: Apache-2.0
compatibility: "Git 2.43+; Worktrunk 0.80+ on macOS, Linux or Windows; bash, zsh, fish, nushell or PowerShell for directory switching"
metadata:
  author: terminal-skills
  version: "2.0.0"
  category: development
  tags: [git, worktree, parallel-agents, cli, workflow]
  repository: https://github.com/max-sixty/worktrunk
---

# Worktrunk

## Overview

A git worktree is a second checkout of one repository in another directory, on another branch.
That is the cheapest isolation available for parallel work: two agents editing two worktrees
cannot overwrite each other's files, yet they share history and can merge locally. Worktrunk's
`wt` binary wraps the bookkeeping. You name a branch and it derives the directory, runs the
project's setup hooks, optionally starts a program there, reports the state of every checkout in
one table, and lands a finished branch with tests run first and the leftovers removed.

Everything below was run against version 0.80.0 in a throwaway repository. Note that
`.worktrunk.toml` and keys such as `on_create` or `[create] copy` do not exist; the real files
and keys are in step 3 and step 4.

## Instructions

### 1. Check the ground

- `git --version` is 2.43 or later and `wt --version` answers. On Windows the command may be
  `git-wt`, because Windows Terminal owns the name `wt`.
- `wt config show` prints both config paths, pending hook approvals, and whether shell
  integration is installed. Read it before changing anything.
- Ask how finished work should land: a local merge into the default branch, or a pushed branch
  and a pull request. This decides step 7.

### 2. Install

Package managers: `brew install worktrunk` (macOS, Linux), `cargo install worktrunk` (any Rust
toolchain), `winget install max-sixty.worktrunk` (Windows). Otherwise take the release archive
and verify it before unpacking:

```bash
base=https://github.com/max-sixty/worktrunk/releases/latest/download
curl -fsSLO "$base/worktrunk-x86_64-unknown-linux-musl.tar.xz"
curl -fsSLO "$base/worktrunk-x86_64-unknown-linux-musl.tar.xz.sha256"
sha256sum -c worktrunk-x86_64-unknown-linux-musl.tar.xz.sha256
tar -xJf worktrunk-x86_64-unknown-linux-musl.tar.xz
install -m 0755 worktrunk-x86_64-unknown-linux-musl/wt "$HOME/.local/bin/wt"
```

Then `wt config shell install`, once. A child process cannot change its parent's directory, so
this adds a shell function that does the `cd`. It edits the user's shell startup file: propose
it, do not run it unasked.

### 3. Decide where worktrees live

The default puts each worktree beside the repository: branch `fix/csv-export` of
`~/code/ledger-api` becomes `~/code/ledger-api.fix-csv-export`. To change it, set one key in the
user config, `~/.config/worktrunk/config.toml` (create it with `wt config create`):

```toml
worktree-path = "{{ repo_path }}/.worktrees/{{ branch | sanitize }}"
```

The default is `{{ repo_path }}/../{{ repo }}.{{ branch | sanitize }}`; a central folder would
be `~/worktrees/{{ repo }}/{{ branch | sanitize }}`. `sanitize` turns `/` into `-`. With the
in-repo layout, add `.worktrees/` to `.gitignore`; otherwise the main checkout shows it as
untracked.

### 4. Write the project hooks

Project settings go in `.config/wt.toml`, committed with the repository
(`wt config create --project` writes a commented starter). Hook names are fixed:

| Moment | Blocks until done | Runs detached |
|--------|-------------------|---------------|
| Any switch | `pre-switch` | `post-switch` |
| A worktree is created | `pre-start` | `post-start` |
| Worktrunk makes a commit | `pre-commit` | `post-commit` |
| `wt merge` | `pre-merge` | `post-merge` |
| A worktree is removed | `pre-remove` | `post-remove` |

A failing `pre-` hook cancels the operation. Use `pre-start` for anything the program launched
with `-x` needs at once (dependencies, env files) and `post-start` for slow or long-lived jobs
(dev server, cache copy). Each hook can be a string, a table of named commands that run
together, or `[[pre-merge]]` blocks that run in sequence.

```toml
[pre-start]
deps = "npm ci"

[post-start]
caches = "wt step copy-ignored"
dev = "npm run dev -- --port {{ branch | hash_port }}"

[pre-merge]
test = "npm test"
lint = "npm run lint"

[list]
url = "http://localhost:{{ branch | hash_port }}"
```

- `{{ branch | hash_port }}` maps a branch name to a stable port between 10000 and 19999, so
  every worktree gets its own dev server and `wt list` shows its URL.
- `wt step copy-ignored` copies gitignored files (`node_modules/`, `.env`, build output) from
  the main worktree into the new one, using copy-on-write on APFS, btrfs and XFS. A
  `.worktreeinclude` file with gitignore-style patterns narrows it; `--dry-run` previews it.
  Python virtual environments hold absolute paths, so recreate them instead of copying.
- Other variables: `{{ worktree_path }}`, `{{ primary_worktree_path }}`, `{{ default_branch }}`,
  `{{ target }}`. Values are shell-escaped already: do not quote them.

Project hooks are repository code, so Worktrunk asks before the first run and whenever a
command's text changes. Without a terminal the prompt cannot appear and the command fails:

```bash
wt hook show                        # read what would run
wt config approvals add             # the user approves; saved in ~/.config/worktrunk/approvals.toml
wt switch --create fix/csv-export --yes   # one-off bypass, nothing saved
```

An agent shows the hook commands to the user and gets a yes before using `--yes`. `--no-hooks`
skips them for one command.

### 5. Create worktrees and start work

| Command | Result |
|---------|--------|
| `wt switch --create feat/audit-log` | New branch from the default branch, new worktree, hooks run |
| `wt switch -c hotfix/tax --base release/2.4` | Same, branching from another base |
| `wt switch feat/audit-log` | Go to the branch's worktree, creating one if it has none |
| `wt switch pr:412` | Check out a GitHub pull request's branch (needs an authenticated `gh`) |
| `wt switch -` / `wt switch ^` | Previous worktree / default-branch worktree |
| `wt switch -c feat/x -x claude -- 'task text'` | Create, then run a program inside it |

`-x` names the program; every argument after `--` is passed to it, so a prompt with spaces stays
one argument. To keep several agents alive, start each in its own terminal multiplexer session:

```bash
tmux new-session -d -s audit-log \
  "wt switch --create feat/audit-log -x claude -- 'Record who changed each invoice and when'"
```

A non-interactive shell has no shell function, so `wt switch` creates the worktree but cannot
change directory. Ask for the path instead and address the worktree explicitly:

```bash
dir=$(wt switch --create fix/webhook-retry --no-cd --format=json | jq -r .path)
wt -C "$dir" list     # JSON keys: action, branch, path, created_branch, base_branch
```

### 6. Watch progress

`wt list` prints one row per worktree: `@` marks the current one, `^` the main one. Status uses
`+` staged, `!` modified, `?` untracked, `↑` ahead of the default branch, `↓` behind,
`↕` diverged, `✗` would conflict, `_` same commit as it with a clean tree, `–` same commit with
uncommitted changes, `⊂` already contained in it.
`--full` adds CI status and needs `gh` or `glab`; `--branches` includes branches without a
worktree.

```bash
wt list --format=json | jq -r '.items[] | select(.default_branch.ahead > 0) | .branch'
wt list --format=json | jq -r '.items[] | select(.display.state == "would_conflict") | .branch'
wt step for-each -- git status --short
```

### 7. Land the work

Local merge, from inside the feature worktree or with `-C`: `wt merge` targets the default
branch, `wt merge develop` another. In order, it commits what is uncommitted, squashes the
branch to one commit, rebases onto the target, runs `pre-merge`, fast-forwards the target, then
removes the worktree and branch. Flags switch stages off: `--no-squash`, `--no-rebase`,
`--no-remove`, `--no-ff` (make a merge commit), `--stage tracked` (leave untracked files out).
Know these before running it:

- By default **untracked files are staged too**. Check `git status --short` first, or pass
  `--stage tracked`.
- The commit message comes from the command in `[commit.generation]` of the user config. With
  none configured the message is a plain `Changes to round.js` (`Squash commits from BRANCH`
  when several commits are squashed); if that is not acceptable,
  commit by hand first and run `wt merge --no-commit`, which requires a clean working tree.
- It merges into the local target and never fetches or pushes. Update the target beforehand and
  push afterwards if a remote should see it.
- A rebase conflict stops the command with the rebase open in that worktree. Resolve it or run
  `git rebase --abort`, then repeat.

Pull-request route: commit, `git push -u origin BRANCH`, open the request, and after it is merged
run `wt remove` in that worktree.

### 8. Clean up

`wt remove` (current worktree) or `wt remove feat/audit-log fix/tax` deletes the directory, and
the branch too when its content is already in the default branch. It refuses a worktree with
uncommitted changes and keeps an unmerged branch; `-f` and `-D` override those two checks and
destroy work, so use them only on the user's explicit word. Removal runs in the background
unless `--foreground` is given. `wt step prune --dry-run` previews removal of merged worktrees.

## Examples

### Example 1: Three agents on one Node service

Marta Olsen wants three independent changes to `~/code/ledger-api` built at once and merged
locally. The `.config/wt.toml` from step 4 is committed and approved (`wt config approvals add`).

```bash
cd ~/code/ledger-api
tmux new-session -d -s csv   "wt switch -c fix/csv-export       -x claude -- 'Quote fields containing commas in the CSV export; add a test'"
tmux new-session -d -s round "wt switch -c fix/invoice-rounding -x claude -- 'Round invoice totals half-even to 2 decimals; add tests'"
tmux new-session -d -s audit "wt switch -c feat/audit-log       -x claude -- 'Record who changed each invoice and when'"
wt list
```

```text
  Branch                Status   main↕  Path                                URL
@ main                    ^             .                                   http://localhost:12107
+ fix/csv-export          ↑      ↑2     ../ledger-api.fix-csv-export        http://localhost:13130
+ fix/invoice-rounding  ! ↑      ↑1     ../ledger-api.fix-invoice-rounding  http://localhost:15280
+ feat/audit-log        ?–              ../ledger-api.feat-audit-log        http://localhost:16726
```

`fix/csv-export` is clean and ahead, so it goes first. She reads `git -C
../ledger-api.fix-csv-export diff main...` and merges:

```bash
wt -C ../ledger-api.fix-csv-export merge
```

`npm test` and `npm run lint` run as `pre-merge`; on success `main` advances by one squashed
commit and the worktree disappears. The next branch's own `wt merge` rebases it onto the new
`main`, which is where overlapping edits surface.

### Example 2: An agent isolating its own task, without shell integration

Asked to fix a retry bug while the user keeps editing `main`, an agent in a subprocess shell:

```bash
wt hook show                                   # shows pre-start "npm ci": user agrees
dir=$(wt switch -c fix/webhook-retry --no-cd --yes --format=json | jq -r .path)
# ... edit files under "$dir", run tests with: npm --prefix "$dir" test
git -C "$dir" add src/webhooks/retry.ts test/retry.test.ts
git -C "$dir" commit -m "fix: back off webhook retries exponentially"
wt -C "$dir" merge --no-commit --yes --format=json
```

```json
{ "branch": "fix/webhook-retry", "committed": false, "rebased": false, "removed": true,
  "squashed": false, "target": "main" }
```

The agent reports the commit now on `main`, the removed worktree and branch, and that nothing
was pushed.

## Guidelines

- Split work so branches touch different files. Worktrees stop agents overwriting each other on
  disk; they do nothing about two branches rewriting the same function.
- One branch can be checked out in one worktree only. To change what a worktree holds, use
  `git switch` inside it; `wt switch` moves between directories.
- Shared resources still collide: a fixed port, one local database, a global cache. Derive ports
  with `hash_port` and database names with `{{ branch | sanitize_db }}`.
- Background `post-` hooks do not print to the terminal. `wt hook post-start --foreground`
  re-runs one visibly, and `-v` on any command prints the resolved template variables.
- User-level hooks in `~/.config/worktrunk/config.toml` run in every repository with no approval
  prompt. Keep them small and never copy one from an untrusted source.
- Any user-config key can be set per command (`--config-set 'merge.squash=false'`) or by
  environment (`WORKTRUNK_MERGE__SQUASH=false`); nested keys are joined with two underscores.
- A dev server started by `post-start` outlives its worktree; stop it in a `pre-remove` hook.
- Every worktree repeats the working files and dependency trees; on filesystems without
  copy-on-write (ext4, NTFS) ten checkouts of a large repository can fill a disk.
- Skip Worktrunk for a single short task on a clean tree, and for work that needs separate
  machines or containers rather than separate directories.

Files in this skill

  • SKILL.md5.3 KB
  • _scores.json1.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…