Run every AI coding task in its own isolated Git worktree, with optional dev services, logs, ports, and safe cleanup.
WorktreePilot (wtp) is a small, local-first CLI for developers using AI coding agents such as Claude Code, Codex, Gemini CLI, Aider, or similar tools.
It helps you run multiple AI coding tasks in parallel without letting them edit the same physical checkout.
WorktreePilot is framework-agnostic by default. The default wtp init creates a generic config with no assumptions about your package manager, framework, platform, or dev servers. Stack-specific setups are available as opt-in presets and examples.
Status:
v0.3.0experimental. Optimized first for WSL + Windows Terminal. Other terminals currently use fallback commands.Installation is currently GitHub/source-based only. WorktreePilot is not published to npm yet.
AI coding agents are starting to behave less like autocomplete and more like parallel contributors.
That creates a real workflow problem.
If multiple agents work in the same repo folder, they can:
- overwrite each other's files
- mix unrelated changes into one working tree
- create messy diffs
- cause hard-to-recover Git conflicts
- contaminate the running dev server
- make review and merge order chaotic
The fix is not “trust the agent more.”
The fix is better isolation.
For each task, WorktreePilot gives the agent its own isolated environment:
- one task — a named unit of work
- one Git worktree — a separate folder on disk
- one branch — isolated commit history
- one agent session — no cross-talk
- optional dev services — web, mobile, API, worker, etc.
- assigned ports — avoids collisions between tasks
- logs and session metadata — inspect what happened
- safe cleanup — stop runtime separately from removing work
WorktreePilot orchestrates the workspace.
You decide what runs inside it.
Clone the repo, install dependencies, build the CLI, and link it locally:
git clone https://github.com/WorktreePilot/worktree-pilot.git
cd worktree-pilot
npm install
npm run build
npm link
wtp --versionAfter npm link, the wtp command should be available globally on your machine.
To update later:
cd worktree-pilot
git pull
npm install
npm run build
npm linkTo unlink:
npm unlink -g worktree-pilotInside any Git repo:
wtp init
wtp doctor
wtp start fix-login-bugThe default config is intentionally minimal. It starts only the agent in an isolated worktree. No install command and no dev services are configured until you add them.
wtp start <task> will:
- Create or reuse
.worktree-pilot/worktrees/<task>. - Create or reuse branch
worktree-<task>. - Open an agent terminal inside the worktree.
- Run optional configured install/service commands.
- Auto-select free ports for configured services.
- Track session state under
.worktree-pilot/sessions/<task>/.
wtp start <task>
│
│ agent works inside its isolated worktree
│
▼
/exit inside the agent
│
│ runtime stops, worktree is preserved
│
▼
review, test, commit
│
▼
wtp finish <task>
wtp finish <task> --remove-worktree
wtp finish <task> --remove-worktree --delete-branch
Important guarantees:
/exitdoes not delete the worktree.wtp stopis runtime cleanup only.wtp finishis post-review cleanup.wtp finish --remove-worktreerefuses to remove a dirty worktree unless--forceis passed.wtp finish --remove-worktreerefuses to remove a worktree if live processes still reference it.
Parallel building is okay.
Parallel merging is not.
wtp init writes this kind of minimal config:
agent:
command: claude
cwd: worktree
interactive: true
captureOutput: false
worktree:
path: .worktree-pilot/worktrees/{{task}}
create: true
branch: worktree-{{task}}
base: HEAD
install:
command: ""
terminal:
provider: auto
services: {}
cleanup:
stopServicesWhenAgentExits: truePlaceholders:
{{task}}— replaced with the task name passed towtp start <task>{{port}}— replaced with the actual port assigned to a service
Empty install command means install is disabled.
Use presets when you want a starting config for a common setup:
wtp init --preset basic
wtp init --preset node
wtp init --preset next
wtp init --preset expo
wtp init --preset pnpm-monorepoOr ask WorktreePilot to inspect the repo and recommend a preset:
wtp init --detect
wtp init --detect --preset <name>--detect is read-only unless a preset is explicitly selected.
These are examples, not defaults.
install:
command: npm install
services:
app:
command: npm run dev -- --port {{port}}
port: 3000
interactive: false
captureOutput: trueinstall:
command: npm install
services:
web:
command: npm run dev -- --port {{port}}
port: 3000
interactive: false
captureOutput: trueSome package managers forward arguments differently. If your script turns into something like next dev -- -p 3000, call the binary directly:
services:
web:
command: pnpm --filter web exec next dev -p {{port}}
port: 3000
interactive: false
captureOutput: trueExpo is interactive. It needs real terminal I/O for QR codes, hotkeys, and split-pane UI.
install:
command: npm install
services:
mobile:
command: npx expo start --lan --port {{port}}
port: 8081
interactive: true
captureOutput: falseinstall:
command: pnpm install
services:
web:
command: pnpm --filter web exec next dev -p {{port}}
port: 3000
interactive: false
captureOutput: true
mobile:
command: pnpm --filter mobile exec expo start --lan --port {{port}}
port: 8081
interactive: true
captureOutput: false
env:
REACT_NATIVE_PACKAGER_HOSTNAME: auto-windows-lan-ipThe service command is just a shell command:
services:
api:
command: cargo run --bin api -- --port {{port}}
port: 8080
interactive: false
captureOutput: true
worker:
command: python -m worker --queue dev
interactive: false
captureOutput: trueSome tools are normal log-producing services. Others are terminal UIs.
| Process kind | Recommended config | Why |
|---|---|---|
| AI agent | interactive: true, captureOutput: false |
Claude/Codex-style TUIs need real stdin/stdout |
| Expo | interactive: true, captureOutput: false |
QR code and key handlers need a real TTY |
| Next.js / API / worker | interactive: false, captureOutput: true |
Logs can be captured safely |
For interactive processes, WorktreePilot logs lifecycle messages but does not pipe the tool's full terminal UI through tee.
For captured services, WorktreePilot writes full output into the session log.
| Command | Description |
|---|---|
wtp init [--preset <name>] [--detect] [--with-skills] [--force] |
Create worktree-pilot.yml. Default preset is basic. --with-skills also installs the Claude Code skills. |
wtp doctor |
Check Git, Node, config, terminal support, ports, WSL/PowerShell when relevant. |
wtp start <task> [--dry-run] [--ip <ip>] |
Create/reuse the worktree and open the agent/services. |
wtp list / wtp ls |
List sessions, worktrees, branches, services, ports, and statuses. |
wtp logs <task> [service] [--tail] [-n <lines>] |
Show logs for the agent or services. |
wtp stop <task> [--agent] [--force] |
Stop runtime processes. Does not remove the worktree or branch. |
wtp finish <task> [--remove-worktree] [--delete-branch] [--force] |
Safe post-review cleanup. |
wtp clean <task> [--force] |
Remove only session metadata. |
wtp audit |
Scan for secrets, runtime artifacts, personal paths, and publish-risk files. |
wtp skills install [--force] [--dry-run] / wtp skills list |
Install opt-in Claude Code skills (wtp-builder, wtp-reviewer) into .claude/skills/. Never installed by default. |
WorktreePilot is currently optimized for WSL + Windows Terminal.
From WSL, wtp start opens tabs using:
wt.exe new-tab --title "<title>" wsl.exe --cd <cwd> -- bash -li -c "exec bash <wrapper>"
This shape matters:
wsl.exe --cd <cwd>opens the tab directly in the right folder.bash -liloads login/interactive shell setup.- interactive tools keep access to a real terminal.
- wrappers write status, logs, PID/PGID metadata, and cleanup signals.
If Windows Terminal is unavailable, WorktreePilot falls back to printing the exact commands you can run manually.
WorktreePilot is agent-agnostic by default. Plain wtp init never creates anything in .claude/ or .agents/.
If you use a supported AI coding agent, there are two optional project skills you can install into your repo to make the builder/reviewer workflow safer:
| Skill | Purpose |
|---|---|
wtp-builder |
Keeps the builder agent scoped to its task worktree. Doesn't switch branches, doesn't merge, doesn't delete. Produces a focused diff and review notes. |
wtp-reviewer |
Reviews one or many active task branches against main. Doesn't modify, doesn't merge, doesn't delete. Returns APPROVE / NEEDS CHANGES / REJECT per branch + a safe merge order. |
Supported agents and install locations:
| Agent | --agent value |
Install dir |
|---|---|---|
| Claude Code | claude |
.claude/skills/ |
| Codex | codex |
.agents/skills/ |
| Both | all |
both of the above |
Install them only when you want them — these are never installed by plain wtp init:
# Standalone (default: Claude Code):
wtp skills install # → .claude/skills/
wtp skills install --agent claude # explicit
wtp skills install --agent codex # → .agents/skills/
wtp skills install --agent all # both
wtp skills install --dry-run # show what would be created
wtp skills install --force # overwrite existing skill files
wtp skills list # see what's bundled / installed (Claude)
wtp skills list --agent codex # check Codex install
wtp skills list --agent all # check both
# Or during init:
wtp init --with-skills # default: claude
wtp init --with-skills --skills-agent codex # Codex only
wtp init --with-skills --skills-agent all # both
wtp init --preset next --with-skills --forceFiles created (depending on --agent):
.claude/skills/wtp-builder/SKILL.md # when agent=claude (default) or all
.claude/skills/wtp-reviewer/SKILL.md
.agents/skills/wtp-builder/SKILL.md # when agent=codex or all
.agents/skills/wtp-reviewer/SKILL.md
If a SKILL.md already exists, wtp skills install skips it and tells you to pass --force to overwrite. (--force on wtp init --with-skills is forwarded to the skills install step.)
Skill bodies are identical across agents; only the invocation syntax differs:
| Agent | Invoke builder | Invoke reviewer |
|---|---|---|
| Claude Code | /wtp-builder |
/wtp-reviewer task1, /wtp-reviewer merge task1 |
| Codex | $wtp-builder |
$wtp-reviewer task1, $wtp-reviewer merge task1 |
The examples below use the Claude / form. For Codex, swap / for $.
The two skills are designed to pair. The reviewer emits a structured Builder Handoff when a branch isn't ready; the builder consumes it as a focused review-fix task. The loop:
-
Start a builder task:
wtp start <task>
-
Use the builder skill in the agent tab:
/wtp-builder <your task prompt> -
When the builder is done, committing is optional. If you want to commit yourself:
cd .worktree-pilot/worktrees/<task> git status git add . && git commit -m "<message>"
You can also leave the changes uncommitted — the builder should report
Uncommitted but ready for reviewin its Review Readiness section. The reviewer can review both committed and uncommitted state, and if you later ask for integration, it will offer to create the commit for you (with explicit confirmation). -
From your main checkout (or a reviewer task), ask the reviewer:
/wtp-reviewer <task> -
If the verdict is
NEEDS CHANGESorREJECT, the reviewer's report ends with a## Builder Handoffblock — copy it into the builder session:/wtp-builder <paste Builder Handoff> -
The builder fixes only the listed issues, runs the listed validations, and replies with a
Review Fix Summaryending inReady For Reviewer: Yes | No. -
Re-run the reviewer:
/wtp-reviewer <task> -
Once the reviewer says
APPROVE, you can integrate. Default is still manual — the reviewer printsApproved Merge Commands(bothmergeandsquashvariants) you run yourself. Or opt into reviewer-driven integration:/wtp-reviewer merge <task> # git merge --no-ff (commits immediately) /wtp-reviewer squash <task> # git merge --squash (staged on main for review) /wtp-reviewer apply <task> # alias for squashBoth still ask for explicit confirmation before touching
main, and again before cleanup.
How the contract works:
- Reviewer produces the handoff. For non-APPROVE verdicts, the handoff is mandatory and copy-pasteable. For APPROVE, no handoff — you get
Approved Merge Commands(both merge and squash variants) instead. - Builder consumes the handoff. Review-fix mode is scoped — no broad refactors, no unrelated edits, no merge, no cleanup.
- Uncommitted task changes are first-class. The builder may leave changes uncommitted. The reviewer can review them, and if you opt into integration, the reviewer asks (once) before creating a commit on the task branch on your behalf.
- Integration requires explicit confirmation. Whether
merge,squash, orapply, the reviewer asks before touchingmain. squash/applyalso asks before the main commit. Changes land staged onmain; the reviewer printsgit diff --stagedfor you and asks again before committing.- Cleanup requires explicit confirmation. After integration + checks pass, the reviewer asks one more time before running
wtp finish --remove-worktree --delete-branch. Never--forceunless you say to discard. - Default reviewer mode is review-only. The reviewer never modifies, merges, or cleans up unless you opt into an integrate mode.
The reviewer has three modes: review-only (default, safe), merge (git merge --no-ff), and squash/apply (git merge --squash — staged on main for final review). Integrate modes always require explicit confirmation gates.
Pass task names — no need to type the full worktree-<task> branch:
/wtp-reviewer task1
/wtp-reviewer task1 task2 task3
/wtp-reviewer check task2
/wtp-reviewer review task2 task3 and recommend merge order
/wtp-reviewer all
How it interprets your input:
- Bare tokens like
task2→ expanded toworktree-task2. Tokens already starting withworktree-are used as-is. - Filler words (
check,review,against,main,and,merge,readiness,recommend,order,branches) are stripped. all/review all/active branches→ reviews every local branch matchingworktree-*.
Each branch gets a structured report: APPROVE | NEEDS CHANGES | REJECT, summary, risky files, possible conflicts with other active branches, required checks, and (only if approved) the exact manual merge commands ending in wtp finish <task> --remove-worktree --delete-branch. After per-branch reports, you get one integration plan with a recommended merge order.
Review-only mode never modifies, merges, or deletes anything. Use it before deciding to merge.
Use one reviewer for all your active branches — that's how it spots cross-branch conflicts.
Prefix the task with merge or integrate:
/wtp-reviewer merge task1
/wtp-reviewer integrate task1
The reviewer then walks a strict step-by-step:
- Review
worktree-task1againstmain. If the verdict is anything other thanAPPROVE, stop and output the Builder Handoff. - Pre-flight on the task worktree. If it has uncommitted changes, the reviewer asks once: "The task has uncommitted changes. Do you want me to create a commit on
worktree-task1before integrating it?" — and only commits after explicit yes + approved message. - Ask explicitly: "Do you want me to merge
worktree-task1into main now?" — wait for a clear yes. git checkout main→ check that working tree is clean →git pull. Aborts on dirty main or failed pull.git merge --no-ff worktree-task1. On conflicts, stops and lists conflicted files — never auto-resolves.- Run typecheck / lint / test (
pnpm typecheck,pnpm lintby default; substitutes for npm/yarn/bun when obvious). If any check fails, stops. - Ask again: "Merge and checks passed. Do you want me to clean up the task worktree and branch now?" — wait for yes.
wtp finish task1 --remove-worktree --delete-branch. Never adds--forceunless you explicitly say to discard the task.
Prefix the task with squash or apply (aliases). This is the review-on-main flow — the task's changes land staged on main so you can review them with git diff --staged before any commit.
/wtp-reviewer squash task1
/wtp-reviewer apply task1
Exact behavior:
- Reviewer reviews
worktree-task1againstmain. - If approved, reviewer asks permission to apply.
- Same uncommitted-task pre-flight as merge mode (asks before committing on the task branch).
- Reviewer runs
git merge --squash worktree-task1. - Changes appear staged on
main. No commit yet. - Reviewer runs checks (
pnpm typecheck,pnpm lint, …). - Reviewer prints: "The task changes are now staged on main. Review them with:
git diff --staged". - Reviewer asks: "Do you want me to commit these staged changes to main now?" — proposes a commit message, waits for yes + approved message.
- Only after commit, reviewer asks: "Do you want me to clean up the task worktree and branch now?"
wtp finish task1 --remove-worktree --delete-branch.
merge vs squash/apply:
merge→git merge --no-ff. Creates a merge commit and integrates immediately on success.squash/apply→git merge --squash. Applies changes as staged changes onmain; nothing is committed until you approve it.- There is no completed merge with no commit. "Merge but don't commit" is the squash/apply flow.
- Review-only is the default. No surprise integrations.
- One branch per invocation. Parallel merging is forbidden.
- Explicit confirmation gates: before integration (merge or apply), before committing (squash/apply only), and before cleanup. Never silently destructive.
- Uncommitted task changes ask once before being committed on your behalf.
- Never
--forcecleanup unless you say so. - Stops on dirty main, on conflicts, on failed checks. Doesn't try to recover by guessing.
Use review-only before integrating. Use merge mode when you're confident the diff is ready and you want a normal merge commit. Use squash/apply mode when you want to eyeball the changes on main before committing.
Parallel building is okay. Parallel merging is not.
A useful pattern is to run one task as the builder and another as the reviewer.
wtp start names-refactorThe builder works only in:
.worktree-pilot/worktrees/names-refactor
When finished:
cd .worktree-pilot/worktrees/names-refactor
git status
git diff
npm test
git add .
git commit -m "refactor: names"Use a separate worktree or your main checkout to review the builder branch.
Prompt:
Review branch worktree-names-refactor against main.
Do not modify files unless explicitly asked.
Check:
- correctness
- hidden side effects
- unnecessary changes
- generated files that should not be committed
- secrets or personal data
- missing tests
- whether the diff matches the task
Merge one branch at a time from the main checkout:
git checkout main
git pull
git merge --no-ff worktree-names-refactor
npm test
git statusThen cleanup:
wtp finish names-refactor --remove-worktree --delete-branchBefore publishing or pushing public changes, run:
wtp auditThe audit checks for common publish risks:
.envfiles- private keys
- local runtime folders
- local worktree folders
- logs
- personal paths
- suspicious API key patterns
- project-specific terms
It is not a security guarantee. It is a practical guardrail.
Claude Code prints Input must be provided either through stdin or as a prompt argument when using --print
The agent likely lost an interactive terminal stream.
Make sure:
agent:
interactive: true
captureOutput: falseThen check:
wtp logs <task> agentYou want:
[wtp] tty stdin: yes
[wtp] tty stdout: yes
If stdout is not a TTY, something is still piping the agent output.
Expo needs real terminal I/O.
Use:
services:
mobile:
command: npx expo start --lan --port {{port}}
port: 8081
interactive: true
captureOutput: falseUse force:
wtp stop <task> --forceIf wtp finish --remove-worktree refuses because a process still references the worktree, that is intentional. Kill the listed process or process group, then retry.
Make sure the command includes {{port}}:
services:
web:
command: npm run dev -- --port {{port}}
port: 3000If WorktreePilot has to auto-bump the port but the command does not contain {{port}}, the service may still try its default port.
- WSL + Windows Terminal is the primary supported path today.
- macOS and native Linux currently use fallback commands.
- Process cleanup is best-effort across detached terminal tabs.
- No cloud, no dashboard, no team mode.
- WorktreePilot isolates the workflow; it does not validate the agent's code.
- Always review and test before merging.
Parallel building is okay. Parallel merging is not.
WorktreePilot is intentionally:
- local-first
- generic by default
- framework-agnostic
- config-driven
- conservative about destructive cleanup
- focused on the developer's machine, not a cloud platform
npm install
npm run typecheck
npm run build
npm run dev -- init
npm run dev -- doctor
npm run dev -- start demo-task --dry-runMIT