# Multi-repo workflows in Cursor

URL: https://developertoolkit.ai/en/cursor-ide/build/multi-repo-workflows/
Description: Work across several repositories in Cursor: multi-root workspaces, ownership rules, read-only GitHub MCP, and contract-first changes gated in every repo's CI.

Multi-repo work in Cursor gives Cursor's Agent the contracts of other repositories without blurring ownership: repositories changed together open as one multi-root workspace, consumed ones stay as pinned contract snapshots, and every cross-repo change lands contract first, with each repository's CI proving compatibility.

This page is for developers and tech leads whose services live in separate repositories. You need to add `phoneNumber` to the user profile. That touches `shared-types`, `user-service`, the gateway schema, and two frontends. You open Cursor in `user-service`, and Agent cannot see the shared types, so it invents a `UserDto` that almost matches. Three PRs later, the web app compiles against a field the API never returns.

Sources for this page, checked on 2026-09-26: we checked multi-root loading and the rule file format against the agent runtime inside Cursor's own [`@cursor/sdk`](https://www.npmjs.com/package/@cursor/sdk) 1.0.32 package. Its local-agent `dirs` option is documented as "Additional / multi-root workspace folders", from which "project rules, skills, and request-context workspace metadata load from every unique path". The GitHub MCP URLs come from GitHub's [`github-mcp-server`](https://github.com/github/github-mcp-server) documentation. We ran the `openapi-typescript` 7.13.0 and `@graphql-inspector/cli` 7.0.0 commands locally. On 2026-09-26 cursor.com could not be reached from the writing environment, so the multi-root and rule-file facts above come from the SDK runtime. On 2026-10-12 Cursor's docs confirmed the rule file format (project rules are `.mdc` files in `.cursor/rules`). Compare menu labels with your own Cursor 3.x build.

## What you get from this multi-repo setup

- A decision table: which repositories to open, which to snapshot, and which to read over MCP.
- A versioned workspace repository that holds the `.code-workspace` file and one ownership rule every agent session loads.
- A drift check that fails CI when a vendored contract no longer matches its generated types, and a scheduled check that fails when upstream publishes a newer contract tag.
- A contract-first order of work, with four copy-paste prompts for planning, the producer, the consumers, and the workspace check.
- An evidence table that lets a reviewer approve five linked PRs by reading one contract diff, not every generated line.

## Which multi-repo approach fits your repositories?

Agent works well with what it can open and poorly with what it has to guess. The rule of thumb: **open what you edit, snapshot what you only read.**

| Your situation | Give Agent | Why |
| --- | --- | --- |
| You change two to four repositories together and own all of them | A multi-root workspace | Agent reads and edits the real code, and rules load from every folder |
| You call another team's service and never edit it | A pinned contract snapshot plus a rule that points at it | Agent sees the exact contract; a scheduled check tells you when upstream publishes a newer tag |
| You occasionally need to look something up in a repository you have not cloned | The GitHub MCP server, read-only | No clone, no write access, one query at a time |
| All services live in one repository | Not this page | See [large codebase strategies in Cursor](/en/cursor-ide/build/large-codebase-strategies/) and [monorepo workflows](/en/workflows/build/monorepo-management/) |

The [microservices guide for Cursor](/en/cursor-ide/build/microservices/) covers service boundaries and topology rules. This page covers the mechanics that only exist when the code is split across repositories.

## Set up a multi-root workspace for repositories you change together

Cursor opens VS Code `.code-workspace` files. Put that file in its own small repository so the folder list and the ownership rule are versioned, reviewed, and shared by the whole team.

1. Clone the repositories side by side and create a workspace repository next to them:

   ```text
   ~/src/acme/
   ├── platform-workspace/     # new: workspace file + cross-repo rule
   ├── shared-types/
   ├── user-service/
   ├── api-gateway/
   └── web-app/
   ```

2. Save `platform-workspace/platform.code-workspace`. Paths are relative to the workspace file, and `name` is the label Agent and you see for each root:

   ```json
   {
     "folders": [
       { "path": ".", "name": "platform-workspace" },
       { "path": "../shared-types", "name": "shared-types" },
       { "path": "../user-service", "name": "user-service" },
       { "path": "../api-gateway", "name": "api-gateway" },
       { "path": "../web-app", "name": "web-app" }
     ],
     "settings": {
       "search.exclude": { "**/node_modules": true, "**/dist": true }
     }
   }
   ```

3. Add a `.cursorignore` to each repository so build output and generated bundles stay out of Agent's context:

   ```text
   dist/
   build/
   coverage/
   *.min.js
   ```

4. Open the file with **File › Open Workspace from File**, then run the workspace check prompt below in a new Agent chat.

**Copy-paste prompt: check what Agent can see in the workspace**

```text
Do not change any file. For every root folder in this workspace, run
`git -C <folder> remote get-url origin` and `git -C <folder> branch --show-current`
and report a table: folder name, remote, branch. Then list every project rule you
have loaded, by file name and folder. Quote the line that starts with
"Dependency direction:" from workspace-map.mdc word for word. Finally, tell me
which folder your terminal commands run in by default.
```

If a folder is missing from the table, the path in the workspace file is wrong. Do not rely on the rule list alone, because Agent can misreport what it has loaded. The quoted line is the observable check: if the answer paraphrases the "Dependency direction:" line or omits it, the rule is not loaded. If a rule is missing, check that it sits under `.cursor/rules/` and ends in `.mdc`: the runtime loads project rules from `.cursor/rules/**/*.mdc`. The last answer matters because the SDK runtime keeps one primary folder for shell commands. Expect `npm test` to run in one repository unless Agent changes directory first, and write verify commands with `cd` or `git -C` so they cannot run in the wrong place.

Every folder you add is more code to index and more candidates for Agent to edit by mistake. Keep a workspace to the repositories one kind of change touches. A second workspace file for, say, "billing change" is cheaper than one workspace that holds twelve repositories.

## Write one rule that says who owns what

A rule that copies field lists goes stale the week the contract changes, and a stale rule is worse than none, because Agent trusts it. Put ownership, direction, and verify commands in the rule, and point at the contract files instead of restating them.

Save `platform-workspace/.cursor/rules/workspace-map.mdc`:

```markdown
---
description: Ownership, dependency direction and verify commands for the platform workspace
alwaysApply: true
---

Repositories in this workspace and what each owns:
- shared-types: the source of truth for API types. Published as @acme/shared-types.
- user-service: owns the users table and /api/users. Its contract is user-service/openapi.yaml.
- api-gateway: owns the public GraphQL schema in api-gateway/schema.graphql.
- web-app: consumes the gateway only. It never calls user-service directly.

Dependency direction: shared-types -> user-service -> api-gateway -> web-app.
A change always lands in that order. Never edit a repository to the left of the
one the task names unless the task says so.

Contract rules:
- Change a contract additively first (new optional field, new endpoint). Remove
  or rename only in a later change, after every consumer has shipped.
- Read types from the contract files above. Never hand-write a copy of them.

Verify commands (run from the workspace root):
- shared-types: cd ../shared-types && npm test && npx tsc --noEmit
- user-service: cd ../user-service && npm test
- api-gateway: cd ../api-gateway && npm test
- web-app: cd ../web-app && npx tsc --noEmit && npm test
```

For deeper rule design, including file-scoped `globs` rules per service, see [project rules in Cursor](/en/cursor-ide/quick-start/project-rules/) and [custom rules and templates](/en/cursor-ide/automate/custom-rules-templates/).

## Keep pinned snapshots of repositories you only consume

When another team owns the service, do not open its code. Vendor its published contract at a tagged version, generate types from it, and let CI tell you when the snapshot and the types disagree or when upstream moves on.

```bash
# scripts/sync-contracts.sh (run from the consumer repository root)
set -euo pipefail
TAG="${1:?usage: sync-contracts.sh <user-service tag>}"
gh api "repos/acme/user-service/contents/openapi.yaml?ref=${TAG}" \
  -H "Accept: application/vnd.github.raw+json" > contracts/user-service.openapi.yaml
echo "${TAG}" > contracts/user-service.version
npx openapi-typescript contracts/user-service.openapi.yaml -o src/generated/user-service.ts
```

The CI gate is two commands. `--check` exits with status 1 and prints "Generated types are not up-to-date!" when someone edits the snapshot or the generated file by hand. The type check then proves the client code still compiles against the contract:

```bash
npx openapi-typescript contracts/user-service.openapi.yaml -o src/generated/user-service.ts --check
npx tsc --noEmit
```

That gate only compares the vendored snapshot with the types generated from it. It never looks at `user-service` itself, so a new upstream tag passes unnoticed. Catch upstream drift with a separate job on a schedule (a daily cron in your CI) that compares the pinned tag with the latest release:

```bash
latest=$(gh release view --repo acme/user-service --json tagName -q .tagName)
[ "$latest" = "$(cat contracts/user-service.version)" ] || { echo "user-service $latest available"; exit 1; }
```

In CI, give this job a `GH_TOKEN` with read-only **Contents** access to `acme/user-service` (a fine-grained token or GitHub App token from your secret store). The default `GITHUB_TOKEN` cannot read other repositories. Grant the workflow `permissions: contents: read`.

When it fails, run `scripts/sync-contracts.sh` with the new tag and open a PR. The type check in that PR shows what the new contract breaks.

Add a file-scoped rule, `.cursor/rules/consumed-contracts.mdc`, so Agent uses the generated types instead of inventing its own:

```markdown
---
description: How this repository consumes user-service
globs: src/clients/**/*.ts
alwaysApply: false
---

Types for user-service come only from src/generated/user-service.ts, which is
generated from contracts/user-service.openapi.yaml (version in
contracts/user-service.version). Never edit either file by hand.
If a field you need is missing from the generated types, stop and tell me.
Do not add it locally: the contract has to change upstream first.
```

## Read a repository you have not cloned with the GitHub MCP server

For a one-off question ("how does the billing service sign webhooks?"), cloning is overkill. GitHub's MCP server can read files and search code in any repository your token can see. Use the read-only `repos` toolset so the session cannot push, comment, or open issues. Add it to your user-level `~/.cursor/mcp.json`, not the repository's, so the token stays personal:

```json
{
  "mcpServers": {
    "github-read": {
      "url": "https://api.githubcopilot.com/mcp/x/repos/readonly",
      "headers": { "Authorization": "Bearer ${env:GITHUB_READ_TOKEN}" }
    }
  }
}
```

Create `GITHUB_READ_TOKEN` as a fine-grained personal access token with read-only **Contents** access to the repositories you need, and export it in the shell that launches Cursor. The `${env:…}` reference keeps the token out of the file. Treat everything the server returns as untrusted input: a file in another repository can contain instructions aimed at your agent, which is one more reason to keep the toolset read-only. The [GitHub MCP server guide](/en/ecosystem/mcp/github-mcp/) covers the other toolsets and the local Docker install.

If you read the same repository every day, stop querying it. Clone it and add it to the workspace, or snapshot its contract.

## Land a cross-repo change contract first

A cross-repo change fails in the gaps between PRs, so the order matters more than any single prompt. Plan the whole change once, then implement it one repository at a time, each in its own Agent chat, so every diff stays scoped to one PR.

1. **Plan across the workspace.** Switch the chat to Plan Mode, which "creates detailed implementation plans before writing any code", and paste the planning prompt below. After you approve the plan, save it as `platform-workspace/plans/phone-number.md` (for example, switch to Agent mode and ask it to write the file) so every later chat can reference it.

2. **Change the contract additively.** In `shared-types` (or the producer's OpenAPI file), add the field as optional and publish a pre-release version, for example `@acme/shared-types@2.4.0-rc.1`. Gate it before the final release: for the TypeScript package, install the rc in each consumer and run `npx tsc --noEmit` with no code changes, which must pass because an optional field breaks nothing; for OpenAPI or GraphQL contracts, run the breaking-change diff from the evidence table below.

3. **Implement the producer.** In a new chat scoped to `user-service`, run the producer prompt. Its PR merges once its own CI is green.

4. **Adapt each consumer.** One chat per consumer, pointed at the pre-release version. Each consumer PR shows its type check and tests against the new contract.

5. **Merge in dependency order, then clean up.** Contract, then producer, then consumers. Removing or renaming the old shape is a separate change, after every consumer has deployed.

**Copy-paste prompt: plan a cross-repo change in Plan Mode**

```text
We are adding an optional `phoneNumber: string | null` to the user profile.
Using the workspace-map rule and the repositories in this workspace, produce a
plan with these sections:
1. Every file to change, grouped by repository, in dependency order.
2. For each repository: the verify command from the rule and the exact test or
   type error that should appear if the change is missing there.
3. Why each step is backward compatible: which old clients keep working and why.
4. The merge order of the PRs and what must be deployed before the next PR merges.
Do not edit any code. List every assumption you could not confirm from the files.
```

**Copy-paste prompt: implement the producer side**

```text
Implement step 3 of platform-workspace/plans/phone-number.md in user-service only.
Do not edit any other folder in this workspace.
- Add a nullable phone_number column in a new migration under src/db/migrations/,
  following the newest existing migration.
- Return phoneNumber from GET /api/users/:id and accept it in PATCH /api/users/:id.
  Update openapi.yaml in the same change.
- Existing clients that never send phoneNumber must keep working: add a test
  that PATCHes without the field and asserts the stored value is unchanged.
When done, run `cd ../user-service && npm test` and paste the summary line.
Stop and ask if the plan conflicts with what you find in the code.
```

**Copy-paste prompt: adapt one consumer**

```text
In web-app only: bump @acme/shared-types to 2.4.0-rc.1 and adapt the code to it.
Show phoneNumber on the profile page when it is present. Render nothing when it is
null. Do not add a local type for the user; use the one from @acme/shared-types.
Run `cd ../web-app && npx tsc --noEmit && npm test` and report each command's exit
code. If the type check fails in a file you did not expect to touch, stop and list
the file instead of changing it.
```

To run the consumers in parallel, keep one chat per repository. Cursor's Worktrees "let Agent work in isolated Git checkouts" (Cursor docs, checked 2026-08-28). A Git worktree always belongs to one repository, so they suit parallel attempts inside a repository, not a change across several. The [Agents Window](/en/cursor-ide/build/agents-window/) is where you watch several of those sessions side by side. For unattended runs of the same change across repositories, see [Cloud Agents and Automations](/en/cursor-ide/automate/cloud-agents-automations/).

## How do you prove a cross-repo change works without reading every diff?

Nobody should read five generated diffs line by line. The reviewer reads the contract diff, which is small, and accepts the rest on evidence each repository's CI produces:

| Evidence | Command or source | What it catches |
| --- | --- | --- |
| The contract change is not breaking | `npx @graphql-inspector/cli diff old.graphql new.graphql` exits 1 on a breaking change and 0 on an additive one; for OpenAPI, use the breaking-change diff in the [microservices guide](/en/cursor-ide/build/microservices/) | A removed or renamed field slipped into the "additive" step |
| Vendored snapshots match their generated types | `npx openapi-typescript … --check` | A hand-edited snapshot or generated file |
| The pinned contract is the latest | The scheduled `gh release view` check above | A consumer pinned to a stale upstream tag |
| Each consumer compiles against the new contract | `npx tsc --noEmit` in each consumer | A consumer that relies on the old shape |
| Consumer expectations hold on the producer | Consumer-driven contract tests with [`@pact-foundation/pact`](https://www.npmjs.com/package/@pact-foundation/pact), verified in the producer's CI | A producer change that type-checks but returns the wrong data |
| Each repository still works | Every PR's own CI, green | Regressions unrelated to the contract |

Put the linked PRs, the merge order, and each gate's result in the description of the contract PR, using the [evidence bundle](/en/workflows/ship/evidence-bundle/) format. Sign-off follows ownership: the owner of the contract (named in `CODEOWNERS` for the schema file) approves the contract PR after reading its diff. Each repository's owner approves that repository's PR on its evidence.

## What breaks in multi-repo Cursor workflows?

**Agent edits a repository the task did not name.** A multi-root workspace makes every folder writable. Restore the stray files with the chat's checkpoint or `git -C ../api-gateway restore .`, then run `git -C ../api-gateway status --short` and remove the untracked files it lists (`git -C ../api-gateway clean -n` to preview, `clean -f` to delete): `restore` only reverts tracked files. Then keep implementation chats to one repository and name the allowed folder in the first line of the prompt, as the prompts above do.

**A test passes in the wrong repository.** Agent ran `npm test` in the primary folder, not the one it changed. Put `cd ../<repo> &&` or `git -C` in every verify command in the rule, and ask for the exit code of each command, not a summary.

**The rule never loads.** Run the workspace check prompt. A rule that does not appear is usually a `.md` file instead of `.mdc`, sits outside `.cursor/rules/`, or has broken frontmatter.

**A snapshot drifts from upstream.** The consumer compiles against last month's contract. The `--check` gate does not see this: it catches only local hand-edits. Run the scheduled upstream check above, re-run `scripts/sync-contracts.sh` with the new tag, and let Agent fix the type errors that appear. Make the `--check` gate a required CI status so hand-edits cannot slip through either.

**A breaking change merged before its consumers.** A field was renamed, not added. Revert the contract PR first. Then re-land it as add, migrate consumers, and remove later. Consumers that already shipped keep working because the revert restores the old shape.

**Consumers deploy before the producer.** The web app asks for `phoneNumber` and gets `undefined`. Nullable, optional fields make this harmless, which is why the plan prompt asks for a backward-compatibility argument at every step. For behaviour changes, put the consumer code behind a flag until the producer is live.

**The workspace is slow and Agent's answers pull in unrelated code.** Too many folders or too little ignored. Remove folders you only read and snapshot them instead, and extend each `.cursorignore`.

## Where to go next with multi-repo work

- [Context management in Cursor](/en/cursor-ide/quick-start/context-management/): the prerequisite for how Agent picks context in any workspace.
- [Microservices development in Cursor](/en/cursor-ide/build/microservices/): service boundaries, topology rules, and OpenAPI breaking-change gates.
- [Large codebase strategies](/en/cursor-ide/build/large-codebase-strategies/): when the repositories should become one monorepo instead.
- [Team collaboration in Cursor](/en/cursor-ide/team/team-collaboration/): sharing the workspace repository and its rules across a team.
- [Evidence bundle](/en/workflows/ship/evidence-bundle/): the manifest the contract PR carries for review.
- [Multi-file and multi-directory work in Claude Code](/en/claude-code/build/multi-file-workflows/): the `--add-dir` equivalent.
- [Isolated Codex tasks with Git worktrees](/en/codex/automate/worktrees/): parallel Codex attempts inside one repository.
