Multi-Repo Workflows skill for Claude Code
Change several repos without breaking any consumer. Claude picks the right setup for the job, from --add-dir for a quick sibling edit to a parent workspace folder whose CLAUDE.md maps every repo, and knows what each setup does not load. Cross-repo changes start with a written plan ordered producer first with a compatibility strategy, every repo gets its own branch and its own test run, and pull requests link to each other with an explicit merge and deploy order.
- name
multi-repo-workflows - size163 lines · ≈1,850 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Runs Claude Code across several repositories or services at once using --add-dir, /add-dir, permissions.additionalDirectories, or a parent workspace folder with a shared CLAUDE.md map, then plans cross-repo changes contract-first, keeps each repo's branch, tests, and conventions separate, and opens linked pull requests merged in a safe order. Use when a change spans a backend and frontend repo, a shared library and its consumers, microservices, or an SDK and its docs, or when Claude cannot see or edit a sibling repository. Try asking
- This API change needs matching updates in our web-app repo — do both
- Set me up so Claude can work across our five service repos in one session
- Bump the shared types package and update every consumer repo with linked PRs
Use it when
- A change spans a library and its consumers, or a backend and frontend repo
- Claude cannot see or edit a sibling repository
- You want a daily setup for several related repos
- Coordinated pull requests need a safe merge and deploy order
Skip it when
- Everything lives in one repository with workspaces — use monorepo-navigation instead
- You want parallel tasks on branches of one repo — use git-worktree-parallelism instead
How to check the result
Each touched repo has its own branch with its own test command passing, local links are removed from lockfiles, and the linked PRs state an order in which no deployed consumer breaks.
The common mistake it prevents
Assuming --add-dir loads the sibling repo's CLAUDE.md, hooks and settings, so Claude edits it with the launch repo's conventions and none of its own checks run.
A cross-repo change is done when every repo it touches has its own branch, its own passing tests run with that repo’s commands, PRs that link to each other with a stated merge order, and no intermediate state where a deployed consumer breaks. Claude should never be guessing which repo’s conventions apply to the file it is editing.
Choose the setup
| Situation | Setup | Why |
|---|---|---|
| Main repo plus occasional reads/edits in one or two siblings | claude --add-dir ../api ../shared or /add-dir mid-session | Quick; grants file access only |
| Same sibling set every day | permissions.additionalDirectories in .claude/settings.local.json | Persists per developer |
| Many repos worked as one system (polyrepo “workspace”) | Launch from a parent folder that contains all clones, with a workspace CLAUDE.md | Each repo’s CLAUDE.md loads when Claude works in it |
| Independent parallel tasks per repo | One session per repo (or worktree), coordinated by a written plan | Clean contexts, no cross-talk |
| A monorepo | Not this skill; see monorepo-navigation | Single git history, different tooling |
What --add-dir does and does not do
- Grants Read/Edit access (and sandbox write access) to the added directories.
- Does not load most
.claude/configuration from them (settings, hooks, agents); skills in an added directory’s.claude/skills/do load. - Does not load the added repos’ CLAUDE.md unless you start with
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1. - The session’s project root, hooks, permissions, and
.mcp.jsonstay those of the directory you launched from.
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 claude --add-dir ../billing-api ../web-app
{ "permissions": { "additionalDirectories": ["../billing-api", "../shared-types"] } }
Workspace folder pattern
~/work/acme/
├── CLAUDE.md # workspace map (below), not committed to any repo
├── billing-api/ # own CLAUDE.md, own .git
├── web-app/
└── shared-types/
Launch claude in ~/work/acme. The workspace CLAUDE.md loads at start; each repo’s
CLAUDE.md loads when Claude reads files inside it. Keep the workspace file to a map and
cross-repo rules:
# Acme workspace
Each subfolder is a separate git repo with its own CLAUDE.md, branch, and test commands.
Always `cd` into a repo before running git or its tests.
| Repo | Role | Test | Default branch |
|---|---|---|---|
| shared-types | Published TS types (npm @acme/types) | `pnpm test` | main |
| billing-api | Go service, consumes shared-types via generated client | `make test` | main |
| web-app | Next.js, consumes billing-api REST | `pnpm test` | develop |
## Cross-repo rules
- Contract changes start in shared-types; publish before consumers depend on them.
- Never commit across repos in one command; one branch and PR per repo.
- API changes are additive first; removals only after all consumers migrated.
Workflow for a cross-repo change
- [ ] 1. Map the change: which repos, which contract (API, schema, types, events)
- [ ] 2. Write the plan with order and compatibility strategy
- [ ] 3. Create one branch per repo with the same name
- [ ] 4. Implement producer first, then consumers, testing each in its own repo
- [ ] 5. Integration check across repos locally (link or pin pre-release)
- [ ] 6. Open linked PRs with merge/deploy order; confirm with the user before pushing
- [ ] 7. After merge: bump pins, remove compatibility shims in a follow-up
1–2. Map and plan
Find every consumer before changing a contract:
for r in */; do (cd "$r" && git grep -n "InvoiceStatus" -- ':!*.lock' | sed "s|^|$r|"); done
rg -n "/v1/invoices" --glob '!**/node_modules/**' .
Write the plan to a file in the workspace (not in any repo), e.g. plan-invoice-status.md:
| Step | Repo | Change | Compatible with old consumers? | Verify |
|---|---|---|---|---|
| 1 | shared-types | add "disputed" to InvoiceStatus | yes (additive) | pnpm test, publish 1.8.0-rc.1 |
| 2 | billing-api | emit disputed; accept old clients | yes | make test, contract test |
| 3 | web-app | render disputed; bump types | n/a | pnpm test, e2e invoice page |
Compatibility strategies: expand-then-contract (add new, migrate, remove old), version the endpoint, feature-flag the consumer, or tolerant readers (unknown enum values handled). Never plan a step where a deployed consumer meets a producer it cannot handle.
3–4. Branches and per-repo work
for r in shared-types billing-api web-app; do git -C "$r" switch -c feat/invoice-disputed; done
- Run each repo’s own test command from inside it (
cd billing-api && make test); a root-levelnpm testin the workspace folder means nothing. - Respect each repo’s conventions; read its CLAUDE.md before editing there.
- For long or parallel work, delegate per repo to subagents with a self-contained brief (repo path, step from the plan, test command, what to return).
5. Integration check
| Ecosystem | Local linking |
|---|---|
| npm/pnpm | pnpm add ../shared-types temporarily, or publish a pre-release tag |
| Go | go work init ./billing-api ./shared-go or a temporary replace directive |
| Python | uv pip install -e ../shared-lib |
| Rust | [patch.crates-io] shared = { path = "../shared" } |
Remove local links before committing; lockfiles must point at published versions.
6. Linked PRs
gh pr create -R acme/shared-types --title "feat: add disputed invoice status" --body-file pr1.md
gh pr create -R acme/billing-api --title "feat: emit disputed status" --body "Depends on acme/shared-types#412. Merge after it is published."
Each body states: depends on, merge order, deploy order, rollback. Pushing and opening PRs are shared-state actions: show the user the list and get confirmation first.
Gotchas
- Hooks and permissions come from the launch directory only. A formatter hook in
web-app/.claude/settings.jsondoes not run when you launched frombilling-api. gitcommands run in the shell’s current directory; always usegit -C <repo>orcdfirst, or you commit to the wrong repo.- Relative paths in CLAUDE.md files are relative to that file; workspace-level imports of repo files need correct relative paths.
- Different package managers, Node versions, or Go versions per repo: check each repo’s
.nvmrc,.tool-versions,packageManagerfield before running commands. - Large sibling repos inflate search noise; scope searches with paths and globs.
- Do not copy code between repos to “unblock” a consumer; depend on the published artifact.
Output
**Change:** add `disputed` invoice status across 3 repos (plan: ~/work/acme/plan-invoice-status.md)
| Order | Repo | Branch | Tests | PR |
|---|---|---|---|---|
| 1 | shared-types | feat/invoice-disputed | pnpm test pass | acme/shared-types#412 (publish 1.8.0) |
| 2 | billing-api | feat/invoice-disputed | make test pass, contract test pass | acme/billing-api#977 (after #412) |
| 3 | web-app | feat/invoice-disputed | pnpm test pass, e2e pass | acme/web-app#1503 (after #977 deployed) |
**Compatibility:** additive enum; web-app tolerates unknown statuses; no breaking window
**Follow-up:** remove fallback rendering after 2 releases About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.