v7.2.2 · MIT · GitHub ★ 17

Keep every branch checked out. Switching is just cd.

sync-worktrees turns each Git branch, in every repo you declare, into a folder on disk, kept in sync with the remote. A new machine is one config file.

Git history is stored once. Work in progress is never touched; unpushed commits are never discarded.

Get started $ npm install -g sync-worktrees

One config

// sync-worktrees.config.js
/** @satisfies {import("sync-worktrees").SyncWorktreesConfig} */
const config = {
  repositories: [
    { name: "frontend", repoUrl: "[email protected]:company/frontend.git", worktreeDir: "./worktrees/frontend" },
    { name: "backend", repoUrl: "[email protected]:company/backend.git", worktreeDir: "./worktrees/backend" },
  ],
};
export default config;

Declare your repos once. Commit the file to a small workspace repo of its own, and every teammate and every new machine rebuilds the same workspace.

The problem

Stash. Switch. Repeat.

Every extra branch is another checkout and another path to remember.

✗ Without sync-worktrees
  • Stash half-finished work to glance at another branch
  • Hunt for where you cloned that sibling repo last month
  • Switch branches in five repos for one cross-cutting feature
  • Onboard new hires through a brittle copy-paste setup doc
With sync-worktrees
  • Clean, fully pushed branches fast-forward themselves on a schedule, with no manual git fetch
  • Switching branches becomes cd
  • Every repo at a predictable path, declared once in config
  • Open the same branch in frontend/ and backend/ at the same time: one folder per branch, at the same path in every repo
  • New machine: one config, one command, the whole workspace appears

See it in 20 seconds

From git stash to cd.

The solution

Declare the workspace once. Get every selected branch at a stable path.

One config. It clones what's missing, makes a worktree per selected branch, and keeps each at a predictable path.

cfg

Create sync-worktrees.config.js

The config decides which repo branches get a folder.

// sync-worktrees.config.js

/** @satisfies {import("sync-worktrees").SyncWorktreesConfig} */
const config = {
  defaults: {
    branchInclude: ["main", "feature/*", "release/*"],
    branchExclude: ["feature/wip-*"],
  },
  repositories: [
    {
      name: "frontend",
      repoUrl: "[email protected]:company/frontend.git",
      worktreeDir: "./worktrees/frontend",
    },
    {
      name: "backend",
      repoUrl: "[email protected]:company/backend.git",
      worktreeDir: "./worktrees/backend",
      branchMaxAge: "6m",
    },
  ],
};

export default config;
What the config buys you

Selected branches only

Keep main, feature, and release branches while excluding wip branches or stale branches by age.

Stable paths

Every branch has a folder of its own, at the same path on every machine: the default branch under its own name, the rest named after the branch with a short suffix that keeps similar names apart. cd into one instead of checking out and stashing.

Same layout on every machine

Commit the config to a small workspace repo of its own (a small .gitignore keeps out the .bare/ and worktree folders the tool writes beside it). A new laptop or a new teammate gets the same folders, same names.

Team workspace in the README →

Worktree mode

Every remote branch that passes your filters gets its own directory, all sharing one Git database. History is stored once, but each branch still costs a working tree. That is what the filters are for.

Clone mode

When one checkout is enough, get a single branch checked out at a fixed path: no .bare/, no per-branch subfolders. Use it for a dependency sibling that expects a fixed relative path.

Interactive TUI

Ink-based terminal UI with wizards for opening worktrees in editor or terminal, creating branches, and inspecting multi-repo status.

Filtering

Glob include/exclude, age-based pruning, sparse checkout for monorepos, per-repo overrides.

Safety by default

It won't touch work in progress: a worktree with uncommitted changes is never removed or replaced, and a sync leaves it alone instead of updating it. Nothing is merged or rebased for you, and removals are recoverable by default.

Optional MCP server

A second binary, sync-worktrees-mcp, speaks MCP over stdio if a client wants it. The folders are ordinary directories either way.

How removal and recovery work →

Quick start

Three commands to a synced workspace.

  1. 1

    Install

    Global npm install. Node 24+, Git (and git-lfs for LFS repos), macOS or Linux; tmux only for the TUI's open-in-terminal wizard.

    terminal
    npm install -g sync-worktrees
  2. 2

    Run the init wizard

    Writes sync-worktrees.config.js where you run it, with your first repository in it. Add more by editing the file.

    terminal
    cd ~/projects/my-sync-dir
    sync-worktrees init
  3. 3

    Launch the TUI, or sync once

    Plain command = TUI that syncs now, then hourly. --runOnce for CI or a cron/systemd timer.

    terminal
    # interactive TUI + cron
    sync-worktrees
    # one-shot
    sync-worktrees --runOnce

When not to use it

Skip it for simple Git setups.

It pays off once you work across many branches and repos. Skip it if:

  • – One repo, one branch you ever touch

    Plain git worktree, or a single clone, is enough.

  • – Tiny repos where re-cloning is instant

    The disk and bookkeeping savings don't matter.

  • – You don't want many persistent branch directories

    sync-worktrees keeps a folder per branch on disk by design.

Optional MCP

The folders are real. MCP is extra.

Anything that can cd already works. A second binary, sync-worktrees-mcp, speaks MCP over stdio if a client wants to list, create, or sync worktrees without guessing paths.

Works from any managed worktree; sync and initialize also need your config, found from a parent directory or loaded at runtime.

Use the Claude Code CLI. --scope user makes the server available in every worktree (the default scope is the one directory you ran it from). It finds your config by walking up from the working directory.

claude mcp add --scope user sync-worktrees -- npx -y -p sync-worktrees sync-worktrees-mcp
Example workflow

Open backend's release/next worktree while you sit in frontend/main.

Both are directories. An editor, a shell, or an MCP client can use them. If you do wire up MCP, the tools look like this:

  1. 1detect_context: config, repos, worktrees, and what works from here
  2. 2list_worktrees: status across repos
  3. 3get_worktree_status: one branch in detail
  4. 4create_worktree: branch a fix
  5. 5sync: fetch, create, prune, update

What an agent cannot do through it

  • –Delete anything directly. sync is the only tool that removes a worktree, with the same checks as the CLI, and it is flagged destructive so clients can prompt.
  • –Move an existing remote branch. Pushes are create-only.
  • –Overwrite an existing directory.
  • –Create a branch your filters would remove again, unless it passes force: true.

Setup for every client, what the server sees from where it is launched, every tool, safety and a parallel-agents recipe: docs/mcp.md.

  • detect_context Call first. Works out which config and repo a path belongs to, lists the worktrees around it, and reports which tools work from there.
  • list_worktrees Every worktree with its status, whether it is safe to remove, and when it last synced, across all repos or one.
  • get_worktree_status Per-worktree detail: dirty files, unpushed commits, stashes, operation in progress.
  • create_worktree Worktree mode only. Check out a branch in its own directory; a new branch is pushed by default, create-only, so an existing remote branch is never moved.
  • update_worktree Fast-forward one worktree to match upstream. Worktree mode only.
  • sync Full sync cycle: fetch, create, prune, update. Needs a loaded config; flagged destructive so clients can prompt.
  • initialize Clone the bare repo and create the default-branch worktree. Needs a loaded config.
  • load_config Load or switch the active config at runtime.
  • set_current_repository Pick the active repo when several are configured.

FAQ

Common questions

Is it safe with uncommitted work?

Yes: uncommitted and untracked work is never touched. A clean tree with nothing unpushed can fast-forward. Committed work that has drifted from upstream is set aside, not silently overwritten; force-push or deleted upstream covers that case. If you keep your own folders in the workspace, leave recovery on.

Open this answer

What's the difference between git worktree and sync-worktrees?

git worktree adds one extra folder. This makes every selected branch a folder. Those folders stay in sync from one config.

Open this answer

Why use this instead of cloning each branch separately?

Cloning each branch copies history every time. Here history is stored once, and each branch is only a working tree. This creates, removes, and refreshes those folders for you. You can limit which branches.

Open this answer

What happens when a branch is force-pushed or deleted upstream?

If you have unique commits and upstream has new ones, the folder is set aside and a fresh upstream checkout takes its place. That work is never merged or overwritten, and you can get it back. If you have no unique commits, a clean tree is brought up to date in place. A branch deleted upstream is removed only if the tree is clean; otherwise it stays.

See trash and recovery.

Open this answer

Can I run it continuously or on a cron?

Leave the TUI open and it syncs on a schedule, hourly by default. Headless, put --runOnce on a cron job or a timer. Overlapping runs are skipped.

Open this answer

Does it work with monorepos?

Yes. Include/exclude and sparse checkout if you only want a slice.

See sparse checkout.

Open this answer

Do I need MCP or an AI client?

No. The folders are ordinary directories, so cd already works, and MCP is an optional extra.

See docs/mcp.md.

Open this answer

Every branch, checked out and kept in sync.

Point it at your repos. Stop stashing to look at another branch.

$ npm install -g sync-worktrees