Run Claude Code across multiple repositories

Many products are split across repositories: a client, a server, the server’s configuration, an admin panel, a landing page, a handful of microservices. Merging them into one repository isn’t always sensible, if only because of who should have access to what.

Claude Code starts in one directory, and by default it has access to the files there. Give it the other repositories and run a few sessions in parallel, and they start tripping over branches: one session checks out a branch in the server repository, and another, working on a different feature, is suddenly looking at the wrong code.

There are two ways to give one session several repositories: add them as extra directories, or start the session in a directory that holds a git worktree of each repository, all on the same branch. Grove, a menu-bar app and command-line tool for macOS, builds the second for you, and plain git can too.

Option 1: one session with extra directories (--add-dir)

Start Claude Code in one repository and add the others with --add-dir:

cd ~/code/acme-shop/web-client
claude --add-dir ../api-server ../server-config

In a session that is already running, /add-dir adds a directory:

/add-dir ../api-server

To have them added every time you start Claude Code in web-client, list them under permissions.additionalDirectories in its .claude/settings.local.json:

{
  "permissions": {
    "additionalDirectories": [
      "../api-server",
      "../server-config"
    ]
  }
}

What this gives the session:

  • File access. Claude can read and edit files in the added directories under the same permission rules as in the one you started in.
  • Only some of their configuration. Claude Code doesn’t discover most .claude/ configuration in added directories. Skills, commands and subagents load from directories added with --add-dir or /add-dir. Their CLAUDE.md files load only when CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 is set. Directories in permissions.additionalDirectories give file access and nothing else.
  • No branches of their own. The added directories are your usual checkouts. When another session checks out a branch in one of them, this session is looking at that branch too.

To have the other repositories’ CLAUDE.md files loaded as well:

CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 \
  claude --add-dir ../api-server ../server-config

Starting Claude Code in the folder that holds all the checkouts gives the same file access without flags, and each repository’s CLAUDE.md loads once Claude works on files in it. The checkouts are still the ones every other session uses.

cd ~/code/acme-shop
claude

Option 2: a workspace of worktrees, one branch across all repositories

A git worktree is an extra working directory of a repository, with its own files and its own checked-out branch, sharing the repository’s history and remotes. A workspace is a directory for one feature that holds a worktree of each repository you pick, all on the same branch. Claude Code starts in the workspace directory:

# start Claude Code here:
~/Workspaces/acme-shop/loyalty-points/
├── api-server/   feat/loyalty-points
└── web-client/   feat/loyalty-points

Compared with option 1:

  • Each workspace has its own checkouts. A worktree has its own HEAD, so checking out a branch in your usual checkout, or in another workspace, leaves this one alone. git won’t check out feat/loyalty-points in any other worktree while this workspace has it.
  • Claude Code sees the whole product. Every repository is under the directory it starts in. A CLAUDE.md at the workspace root loads at launch, so it is the place for notes on how the repositories fit together. Each repository’s own CLAUDE.md and skills load once Claude works on files in that repository.
  • Project settings come from the workspace directory. Claude Code reads .claude/settings.json from the directory it starts in, so hooks and permission rules committed in a repository don’t apply to a session started at the workspace root.

In Grove

A Grove project is the folder that holds your repositories. Add it once: click the tree icon in the menu bar, open Settings, press Add project… and pick the folder. Then open the project and, on its Workspaces tab, press + Workspace:

  1. Type a name. The branch comes from the project’s template, feat/{name} by default, and you can change it.
  2. Every repository is ticked; untick the ones the feature doesn’t touch. Each starts from its own base branch, detected from origin/HEAD or set in the project’s settings, and the from menu next to it picks another. Fork from can start them from another workspace’s branches instead.
  3. Press Create. Grove runs git worktree add in each repository, then links or copies the project’s seed files, such as CLAUDE.md, and runs the post-create hooks you set.
  4. Press New Claude on the workspace. The launch sheet picks the account, model and effort, and opens Claude Code in the workspace directory, in cmux or Terminal.app.
The New Workspace form: the name loyalty-points, the branch feat/loyalty-points from the template feat/{name}, Fork from set to base branches, and five repositories ticked, each with its own base branch.
Each repository starts from its own base branch. Screenshots show made-up demo data.

With the grove command-line tool

The CLI does the same from a terminal or a script, and installs with Homebrew:

brew install efim0v/tap/grove-cli

Then create the workspace and start Claude Code in it:

grove create ~/code/acme-shop loyalty-points \
  --repos api-server,web-client
cd ~/Workspaces/acme-shop/loyalty-points
claude

grove create takes the folder that holds the repositories and a workspace name. --repos picks repositories by folder name; without it, every repository Grove finds in the folder goes in. --branch sets the branch name, and --from stacks the new workspace on another one. The CLI uses Grove’s default layout, ~/Workspaces/<project>/<name> on feat/<name>, and none of the app’s per-project settings, so it links no seed files and runs no hooks.

Doing it by hand with git

A workspace is plain git, so you can build one without Grove. In each repository, fetch, then add a worktree on the feature branch, starting from that repository’s base branch:

cd ~/code/acme-shop
ws=~/Workspaces/acme-shop/loyalty-points

git -C api-server fetch origin
git -C api-server worktree add \
  --no-track -b feat/loyalty-points \
  "$ws/api-server" origin/main

git -C web-client fetch origin
git -C web-client worktree add \
  --no-track -b feat/loyalty-points \
  "$ws/web-client" origin/main

cd "$ws"
claude
  • git worktree add creates the missing directories, so there is no mkdir step.
  • Put each repository’s own base branch in place of origin/main.
  • --no-track keeps git from making origin/main the new branch’s upstream, which it does by default when the start point is a remote-tracking branch. With that upstream, git status compares the branch with origin/main, and a plain git push refuses by default because the names differ. A branch Grove creates from origin/main does get that upstream: Grove doesn’t pass --no-track. Either way, the first push is git push -u origin feat/loyalty-points.
  • git fetch comes first because nothing else here fetches: the worktree starts from origin/main as your clone last saw it. Grove doesn’t fetch either; its git commands never fetch, pull or push.

What Grove adds:

  • Branch names from a template, feat/{name} by default.
  • A base branch per repository, detected from origin/HEAD or set by hand, or another workspace’s branches, so workspaces can be stacked.
  • Seed files: CLAUDE.md or any other file in the project folder, linked or copied to the workspace root or into each repository.
  • A post-create hook per repository: a shell command that runs in its new worktree.
  • When a creation fails partway, the app offers to roll back what that run made: its worktrees and the branches it created.
  • Each workspace’s Claude Code sessions, across accounts, shown as running, waiting or closed.

Which one to use

ApproachGives the sessionUse it for
--add-dirMore repositories, on whatever branch each checkout is onOne session at a time
A workspaceA worktree of each repository, all on the feature’s branchSeveral features in parallel, each across repositories
--worktreeA worktree of the repository you run it in, on a new branchParallel sessions in a single repository

Claude Code’s own --worktree starts a session in a new worktree, by default under .claude/worktrees/<name>/ on a branch named worktree-<name>. If your product lives in one repository, that may be all you need:

claude --worktree feature-auth

For the differences in detail, see Grove workspaces vs claude --worktree.

Questions

Does every repository need the same base branch?

No. Each repository starts from its own. In Grove it is detected from origin/HEAD, set per repository in the project’s settings, or picked on the New Workspace form. With the CLI, --from-branch sets it for one repository, and you can repeat it:

grove create ~/code/acme-shop loyalty-points \
  --from-branch api-server=origin/develop

Use origin/develop unless the repository has a local develop branch. Given a name that exists only on the remote, git worktree add puts the worktree on that branch and doesn’t create the feature branch.

What if the feature branch already exists?

If a repository already has a local branch of that name, Grove checks it out in the new worktree instead of creating it. Grove doesn’t look at the remote: when the branch exists only on origin, it creates a new local branch from the base branch, so create the local branch first.

By hand, leave out -b and --no-track and name the branch in place of the start point. git checks out the local branch or, if there is none, creates one that tracks the branch on origin:

cd ~/code/acme-shop
ws=~/Workspaces/acme-shop/loyalty-points
git -C api-server worktree add \
  "$ws/api-server" feat/loyalty-points

Either way, git refuses if the branch is checked out in another worktree, your usual checkout included.

How do I remove a workspace?

Remove each worktree with git worktree remove, then the directory:

cd ~/code/acme-shop
ws=~/Workspaces/acme-shop/loyalty-points

git -C api-server worktree remove "$ws/api-server"
git -C web-client worktree remove "$ws/web-client"
rmdir "$ws"

git refuses to remove a worktree with modified or untracked files unless you add --force, and rmdir stops if anything else is left, such as a seeded CLAUDE.md. The branches stay until you delete them. Grove has no button for this: a workspace is made of ordinary worktrees and branches in your repositories.

If a worktree’s directory was deleted first, git worktree prune clears what git still records about it:

git -C ~/code/acme-shop/api-server worktree prune

Does this work with agents other than Claude Code?

A workspace is an ordinary directory, so you can start any command-line agent in it yourself. Grove itself starts, lists and moves Claude Code sessions only.