worktree-switcher
v0.1.3
Published
Run many git worktrees of one app on the same fixed dev ports: switch which worktree serves them (or proxy the ports to it), with a floating button and an MCP server for coding agents. Windows.
Maintainers
Readme
worktree-switcher (wts)
Run many git worktrees of the same app on one machine, all wanting the same fixed dev ports, and choose which worktree the browser sees. Built for parallel, agent-driven development (Orca, Claude Code, Codex) on Windows.
┌─▶ worktree feat/login (agent A)
http://localhost:5173 ──────┼─▶ worktree fix/upload (agent B) ← you pick which one; wts does the rest
└─▶ worktree mainwts switchmakes this worktree the one serving the app's fixed ports.- Two modes. In run mode wts stops the old worktree's servers and starts this one's. In proxy mode every worktree runs its own servers (hot reload stays on) and wts forwards the fixed ports to the selected one, so switching is instant.
- A floating button always shows which worktree you are looking at. Its menu lets you switch, restart and lock. An optional Auto mode follows the workspace you select in Orca.
- Built for agents. An MCP server (
worktree-switcher) gives coding agents tools to run, preview and restart the app the right way. A Claude Code hook stops them from starting dev servers behind your back. - Safe by default. wts only stops processes it can prove belong to a worktree of the repo. Anything else on a port is reported, never killed.
Why this exists, in detail: docs/why.md. How it works inside: docs/how-it-works.md.
Contents
- Requirements · Install · Quick start · Using it in your project
- Run mode vs proxy mode · wts.json
- Commands · The floating button (
wts watch) - Coding agents (MCP + hook) · Orca
- Safety rules · Limitations · Troubleshooting
- Contributing · License
Requirements
- Windows 10 / 11. Process and port handling use Windows APIs; macOS and Linux are not supported yet.
- Node.js 22+
- git, with worktrees
- Windows PowerShell 5.1 (built into Windows), used for port/process queries and the floating button
Install
From npm:
npm install -g worktree-switcher
wts setup # register the MCP server and the agent hook in Claude CodeThen start a new agent session; MCP tools are loaded when a session starts.
Install with your AI agent
You can also just tell your agent: "Install this MCP server: https://github.com/YitFei/worktree-switcher".
Agents installing this for a user: run
npm install -g worktree-switcher, thenwts setup.wts setupregisters the MCP server and installs the hook; do not runclaude mcp addyourself instead. Then tell the user to start a new session so thewts_*tools load, and to ask you to "set up wts" in their project (wts_init, after asking run or proxy mode).
From source:
git clone https://github.com/YitFei/worktree-switcher.git
cd worktree-switcher
npm install && npm run build && npm link
wts setupwts setup does two things:
- registers the MCP server
worktree-switcherin Claude Code (user scope); - adds the PreToolUse hook to
~/.claude/settings.json. It backs the file up first and changes nothing else in it.
It also prints the config snippet for Codex.
Re-run wts setup after upgrading or moving the install. wts setup --uninstall removes both.
Options: --dry-run, --no-mcp, --no-hook.
Updating
npm install -g worktree-switcher@latest
wts --versionThe MCP server is not a service that updates itself: each agent session starts wts mcp when the
session starts and keeps that process. After an update:
- start a new agent session, or reconnect in a running one (
/mcpin Claude Code); - restart
wts watch(menu → Exit, thenwts watch).
wts setup does not need to run again unless the install moved (for example from npm link to a
global install). Release notes: CHANGELOG.md.
Registering the MCP server by hand
wts setup is the recommended way. If you prefer to register the server yourself, install the
package globally first, then:
# Claude Code
claude mcp add --scope user worktree-switcher -- wts mcp// Cursor, VS Code and other MCP clients (mcpServers / servers section of their config)
{ "worktree-switcher": { "command": "wts", "args": ["mcp"] } }# Codex: ~/.codex/config.toml
[mcp_servers.worktree-switcher]
command = "wts"
args = ["mcp"]If a client cannot start wts on Windows (it is a .cmd file), use cmd with
["/c", "wts", "mcp"] as the arguments. Registering by hand installs no hook: agents get the
tools and the rules, but nothing blocks them from running npm run dev themselves. Add the hook
with wts setup --no-mcp.
Quick start
This repository is its own demo (examples/demo). It has two tiny Node.js servers with no dependencies:
- a frontend (
web.cjs, port 4173): a page with one button, coloured and labelled by worktree; - a backend (
api.cjs, port 4241): JSON saying which worktree it runs from.
The page asks the backend and shows both answers, so you can see that the frontend and the backend come from the same worktree:
┌──────────────────────┐
│ [ wts-b ] │ ← button colour and label come from the worktree's folder name
└──────────────────────┘
web from C:\src\wts-b
api from C:\src\wts-bThe repo's own wts.json runs them (run mode):
{
"readyTimeoutSec": 30,
"services": {
"api": { "dir": "examples/demo", "port": 4241, "cmd": "node api.cjs" },
"web": { "dir": "examples/demo", "port": 4173, "cmd": "node web.cjs" }
}
}Try it (in a clone of this repository):
git clone https://github.com/YitFei/worktree-switcher.git && cd worktree-switcher
git worktree add ../wts-a
git worktree add ../wts-b
cd ../wts-a && wts switch # http://localhost:4173 shows a button labelled "wts-a"
cd ../wts-b && wts switch # refresh: "wts-b" (wts-a's servers were stopped first)
wts status
wts watch # the floating buttonTo simulate real work, set const COLOR = "red"; in one worktree's examples/demo/web.cjs and
run wts restart there; the other worktree keeps its own code. The demo README also shows the
same demo in proxy mode.
Using it in your project
1. Create wts.json. Either ask your agent to "set up wts" (it asks you which mode, then
calls wts_init), or run it yourself:
wts init --mode run # detects your dev servers and prints a proposed wts.json
wts init --mode run --write # writes it (use --mode proxy for proxy mode)2. Commit wts.json to your main branch, so every worktree (and every new one Orca creates)
has it. A worktree without it is not managed by wts and does not appear in the menu.
3. Day to day. It depends on the mode (which one?):
Run mode (the default): wts starts and stops the servers.
cd <worktree you want to see> && wts switch # starts its servers on the fixed ports
cd <another worktree> && wts switch # stops the first one's servers, starts these
wts restart # restart after a change that needs it
wts watch # optional: the floating button, switch with a clickAgents use wts_switch / wts_restart instead of npm run dev; the hook blocks the latter.
Proxy mode: you run the servers, wts forwards the fixed ports.
wts watch # FIRST, and keep it running: it holds the fixed ports (the proxy)
cd <worktree> && wts port # this worktree's assigned port(s) and the start command
npm run dev -- --port 5175 # start the servers yourself, on the assigned ports
wts switch # (or the button menu) forward the fixed ports to this worktreeWithout a running wts watch (or wts proxy) nothing listens on the fixed ports in proxy mode.
Agents ask wts_port for their ports and start their own servers there.
Run mode vs proxy mode
| | run (default) | proxy |
|---|---|---|
| Who starts the servers | wts, using cmd from wts.json | you (or your agent), e.g. npm run dev |
| Worktrees running at once | one | all of them, each on its own port |
| What a switch does | stops the old worktree's process tree, starts this one's, waits until the ports listen | changes where the fixed ports forward to; instant, nothing stops |
| Hot reload | yes, for the running worktree | yes, in every worktree |
| Memory | low | one set of servers per worktree |
| What your backend must do | nothing | accept a different port (e.g. --port, or read it from PORT) |
run: browser ──:5173──▶ [ worktree B's Vite ] (A's servers were stopped)
proxy: browser ──:5173──▶ [ wts proxy ] ──▶ :5175 worktree B's Vite
╲─ :5174 worktree A's Vite (still running)Which one to pick, and why local development needs fixed ports at all (OAuth redirects, webhooks, CORS, other services): see the detailed comparison.
In proxy mode, the frontend's own API proxy keeps pointing at the fixed API port (for example
Vite's /api → localhost:3000). The wts proxy forwards that port to the same worktree, so
frontend and backend always stay paired. No app code changes.
wts.json
wts.json goes at the root of the repository. Commit it, so every worktree has it.
Run mode
{
"readyTimeoutSec": 180,
"services": {
"api": { "dir": "backend", "port": 3000, "cmd": "npm run dev" },
"web": { "dir": "frontend", "port": 5173, "cmd": "npm run dev -- --strictPort" }
}
}Proxy mode
{
"mode": "proxy",
"services": {
"api": { "dir": "backend", "port": 3000, "targets": "3001-3099" },
"web": { "dir": "frontend", "port": 5173, "targets": "5174-5199" }
}
}| key | meaning | required |
|---|---|---|
| mode | "run" (default) or "proxy" | no |
| readyTimeoutSec | run mode: how long to wait for the ports to listen (default 120) | no |
| services.<name>.dir | folder to run in, relative to the worktree root (default .) | no |
| services.<name>.port | the fixed port the browser or app uses | yes |
| services.<name>.cmd | run mode: the start command, run as-is in dir | run mode |
| services.<name>.targets | proxy mode: the port range where each worktree runs this service | proxy mode |
Tips:
- With Vite in run mode, use
--strictPort. A busy port then fails instead of silently moving Vite to the next one. - For .NET hot reload in run mode, use
dotnet watch runand setDOTNET_WATCH_RESTART_ON_RUDE_EDIT=trueso it never waits for a keypress.
In proxy mode each worktree gets a stable assigned port per service, inside targets.
wts port shows that port and the command to start the service on it.
Commands
| command | what it does |
|---|---|
| wts status | Who serves each port (worktree, pid, whether wts started it) and the lock. In proxy mode it also shows the selected worktree, every worktree's ports, and what holds each fixed port. |
| wts switch [--force] | Run mode: stops the current owner, starts this worktree's services and waits until they are ready. Proxy mode: forwards the fixed ports here. |
| wts restart [--force] | Run mode: restarts this worktree's services. |
| wts stop [--force] | Run mode: stops the repo's services. Proxy mode: clears the selection; your servers keep running. |
| wts logs [service] [-n 50] [-f] | Run mode: shows service logs (-f to follow). |
| wts lock [--note "..."] / wts unlock | Blocks other worktrees (and agents) from switching or stopping. |
| wts port [service] | Proxy mode: this worktree's assigned port(s) and start command. With a service name it prints just the number. |
| wts watch [--auto\|--manual] [--delay 3] [--no-orca] [--ui float\|tray\|none] | Shows the floating button, runs the proxy, and optionally auto-switches. |
| wts proxy | Proxy mode: runs only the forwarder, without the button. |
| wts focus [path] | Tells a running wts watch which worktree you are on (for editor integrations). |
| wts init --mode run\|proxy [--write] [--overwrite] | Detects the dev servers and proposes (or writes) a wts.json. |
| wts setup [--uninstall] [--dry-run] [--no-mcp] [--no-hook] | Registers the MCP server and the hook in Claude Code. |
| wts --version | The installed version. |
| wts mcp / wts hook | The MCP server (stdio) and the Claude Code PreToolUse hook. Your agent starts these, not you. |
--force only overrides another worktree's lock. Exit codes: 0 ok, 1 failure, 2 refused.
The floating button (wts watch)
- Dot colour:
- green: serving;
- yellow: switching;
- red: a server is missing, or something else holds a fixed port;
- gray: stopped, or nothing selected.
- Name. Click it (or right-click anywhere on the button) to open the menu:
- The project's worktrees; click one to switch to it. Worktrees asleep in Orca are left out (the running one always stays); wake one in Orca to see it again.
- The routes, for example
:5173 ──→ :5175 web: green when the route works, red when it does not. - Each worktree's ports.
:5176?means the port is assigned but nothing is running on it. - ↻ Restart, and Logs ▸ (run mode): one entry per server, plus All. Each opens a terminal
window that follows that server's output live (
wts logs <service> -f), since wts runs the servers in the background. A server that failed (not running while the others are, or its port held by another program) is shown in red, both in Logs and in its route line. - Lock / Unlock, Stop.
- Settings ▸ Auto-switch (follow Orca), Show all projects, Flash on switch.
- Exit.
- Hover any item for what it does.
- ↻
- Run mode: restarts the running worktree's servers.
- Proxy mode: re-detects the servers and resets connections. To restart your own server, use its terminal.
- Manual / Auto
- Manual (the default): wts switches only when you ask (from the menu,
wts switch, or an agent). - Auto: wts follows the worktree you select in Orca (or via
wts focus) once you stay on it for--delayseconds. Auto only reacts when the selection changes, so it never undoes a manual switch.
- Manual (the default): wts switches only when you ask (from the menu,
More about the button:
- Flash on switch (on by default): when another worktree takes over the ports, the button
flashes a moving gradient twice. This happens however the switch was made: the menu,
wts switch, an agent, or Auto. Turn it off in the menu. - Drag it anywhere; it remembers its position.
- It never takes keyboard focus.
- Your choices (Auto, Show all projects, Flash on switch) are saved in
~/.wts/watch.json. --ui trayshows a tray icon with the same menu instead;--ui noneshows nothing.- One
wts watchserves every project.
Coding agents (MCP + hook)
After wts setup, every new Claude Code session has the worktree-switcher MCP server:
| tool | what it does |
|---|---|
| wts_status | Who serves the ports, whether that is the agent's worktree, and the lock. In proxy mode also: assigned ports, start hints, what holds each fixed port, and warnings. |
| wts_switch | Runs or selects the agent's worktree. Refused when another worktree holds the lock. |
| wts_restart | Run mode: restarts the agent's servers, instead of the agent killing and restarting them by hand. |
| wts_stop | Run mode: stops the servers. Proxy mode: clears the selection. |
| wts_logs | The last lines of the service logs. |
| wts_port | Proxy mode: the agent's assigned ports and start commands. |
| wts_init | Detects the dev servers and proposes or writes a wts.json. The agent must ask you run or proxy first. |
The server's instructions tell agents to:
- never start dev servers or kill processes on their ports themselves (run mode);
- use
wts_restartto restart; - in proxy mode, start their own servers on the assigned port, and never point the app's own proxy settings at an assigned port;
- never override a lock;
- report refusals to you instead of working around them.
The hook (wts hook, a Claude Code PreToolUse hook) enforces the run-mode rule. In a run-mode
worktree it blocks these commands and points the agent at wts_switch:
npm,pnpm,yarnorbunwithdev,startorserve;vite,next dev,dotnet runanddotnet watch;- any
wts … --force.
Limits of the hook:
- It ignores quoted text and heredocs, so a commit message that mentions
npm run devis fine. - For the same reason it does not catch
bash -c "npm run dev". - It never blocks anything outside wts-managed worktrees, and it allows everything if it hits an internal error.
For Codex, wts setup prints the ~/.codex/config.toml snippet. For other MCP clients, run
node <install>/dist/src/cli.js mcp over stdio.
Orca
Orca creates one git worktree per task or agent, but it does not manage dev servers or ports for local worktrees. wts fills that gap:
- For Auto mode,
wts watchreads which workspace is selected in Orca from Orca's local state database; no plugin is needed. That database is Orca's internal format, not a public API. If an Orca update changes it, Auto stops following Orca, and everything else keeps working. - Commit
wts.jsonto the branch Orca creates workspaces from, so new workspaces have it. - Orca's
worktree.sharedDirectoriessetting (in orca.yaml) can sharenode_modulesbetween worktrees.
Safety rules
wts only stops a process on a port if it belongs to a worktree of this repo. That means one of:
- wts started it. wts records the process tree and the creation time, so a reused PID is never mistaken for it.
- Its executable or command line contains a worktree path.
Anything else is reported and left running.
The proxy never takes a port that another program already serves; it waits until the port is free. It only accepts connections from this machine.
Locks (
wts lock) stop other worktrees and agents from switching away from what you are testing.Docker and other shared infrastructure are never touched.
State lives in two places:
<git-common-dir>/wts/, shared by all worktrees of a repo: state, lock, assigned ports, logs.~/.wts/: button preferences and position, remembered projects.
Limitations
- Windows only for now.
- Auto mode depends on Orca's internal state database.
- Proxy mode needs every backend to accept a port other than its fixed one.
- The hook matches command text, so commands hidden inside quotes (
bash -c "…") are not caught. - So far it has been tested on only a few machines. Please report issues.
Troubleshooting
| symptom | what to do |
|---|---|
| wts switch is refused: the port is held by a process that is not a worktree of this repo | wts status names the process. Stop it yourself; wts never kills unknown processes. |
| The button is red and says ":5173 held" | Something started on the fixed port outside the proxy, often a server left over from a removed worktree. The tooltip and wts status name it. |
| The page says "wts: … has no 'web' server running" | Proxy mode: the selected worktree has no server. Start it on the port wts port shows, or select another worktree. |
| A worktree is missing from the menu | It has no wts.json yet. Merge the branch that contains it. |
| The agent cannot see the tools | Start a new agent session (or reconnect via /mcp), and check claude mcp list. |
| The button does not appear | Run wts watch in a terminal and read its output. |
Contributing
Contributions are welcome. See CONTRIBUTING.md for the setup, the project layout, how to test safely, and the release steps.
License
MIT © 2026 Yif
