ShipifyAI with multiple repositories

ShipifyAI with multiple repositories

Software product split across multiple repositories

The earlier guides describe ShipifyAI as one project per repository, which works cleanly when a piece of work lives end to end inside that repository. Real product teams rarely have that luxury. A typical web product has a backend in one repository and a frontend in another, sometimes a mobile client in a third, and a shared design-tokens or API-schema package in a fourth. A feature that adds a new endpoint and a screen to consume it touches at least two of those repositories. A bug in a list view that turns out to be caused by a backend serialiser touches at least two of those repositories. ShipifyAI does not need to learn about this; the existing one-project-per-repo model handles it cleanly when the tasks are split and the merge requests are used as the glue between them.

This article walks through that pattern for a fictional SaaS team that ships a NestJS backend and a React frontend as two separate repositories, with a single Jira board across both. It covers how to model the projects in the dashboard, how to break work down so the agents can pick it up independently, how cross-repository context flows through linked merge requests, and how to configure a Grafana MCP server so the backend agent can investigate production bugs from the logs

The scenario

The team is six engineers shipping a SaaS analytics product. The backend repository, `acme/api`, is a NestJS service with PostgreSQL and BullMQ workers. The frontend repository, `acme/web`, is a React + Vite SPA that consumes the API. The team uses a single Jira project with two components, `backend` and `frontend`. Tasks are written as either backend-only, frontend-only, or as an epic with a `backend` child and a `frontend` child that gets created once the API contract is settled.

The team wants ShipifyAI to take the backlog of small, well-described tasks — the ones that would otherwise sit untouched for a sprint because nobody has bandwidth — and produce reviewable merge requests. They also want it to help investigate a recurring class of production bug where logs from Grafana would point to the root cause faster than a human reading them by hand. Critical paths, anything touching auth, billing, or migrations, stay with the humans.

Two ShipifyAI projects, one per repository

The dashboard configuration is two ShipifyAI projects. The first one, "Acme API", connects to the `acme/api` repository through its GitHub PAT, to Jira through the team's Atlassian credentials, and to the `#dev-ai-backend` Slack channel. The second one, "Acme Web", connects to the `acme/web` repository through a separate GitHub PAT scoped to that repository, to the same Jira through the same Atlassian credentials, and to a parallel `#dev-ai-frontend` Slack channel. The two projects are independent on the dashboard, run independent worker containers, hold independent Claude credentials, and can be paused or rotated separately.

The split into two Slack channels is deliberate. Backend reviewers and frontend reviewers are usually different people, the merge request notifications would otherwise mix, and shutting one project down for the day is easier when each one has its own channel to mute. Teams that have a single small group reviewing everything can collapse both into one channel without losing anything; the dashboard supports either choice.

The Jira board can be shared between the two ShipifyAI projects by configuring each project to pick from a different source column. The simplest layout adds two columns, "Ready for AI (backend)" and "Ready for AI (frontend)", to the existing workflow. The backend project's source column is the first one, the frontend project's is the second. The In Progress, In Review, and Incomplete columns can be shared since every Jira issue carries the component label that identifies which agent worked on it. A team that prefers two boards entirely — one per component — can do that instead; the ShipifyAI model does not care, as long as each project points at a source column the other one does not also read from.

Cross-repository work through linked merge requests

The model assumes one repository per task. A task that requires both backend and frontend changes is therefore not one task but two. The pattern the team uses is: an epic in Jira describes the feature in product terms; the engineer breaking it down creates one backend child issue and one frontend child issue; the backend issue is moved to "Ready for AI (backend)" first; once the backend agent has produced a merge request, the merge request URL is copied into the frontend issue's description before that frontend issue is moved to "Ready for AI (frontend)".

The frontend agent now starts with the URL of the backend MR in its task context. The agent cannot reach into the sibling repository directly — its GitHub PAT is scoped to `acme/web` only, the same way the backend's PAT is scoped to `acme/api` only, and ShipifyAI does not weaken that scope to cross repositories. What the agent can do is extract the structured information the human breaking the work down has already attached to the task: the MR description (pasted into the task by the engineer who created the dependent issue), a copy of the relevant API contract excerpt, the request and response shapes, the new error codes. The frontend task is, in effect, a hand-off note plus a link the human reviewer can follow when checking that the consumer matches the producer. When the backend MR merges and the contract changes during review, the engineer updates the frontend task description and the agent picks the change up on its next iteration. The MR link is provenance, not a fetch handle.

The same pattern works in reverse for bugs. A bug reported by support lands as a single Jira issue describing the symptom. The first agent that picks it up is typically the backend one, because logs and traces are the first place to look. If the agent's investigation concludes that the root cause is a backend bug, it produces a backend MR and the issue moves to "In Review". If the investigation concludes that the symptom is a backend bug but the user-facing handling is a frontend bug — for example, the backend correctly returns a 422 with a structured error code, but the frontend renders a generic "Something went wrong" — the engineer triaging the result creates a follow-up frontend issue, pastes the backend MR URL into its description, and moves it to "Ready for AI (frontend)". The two MRs are linked through Jira issue references and through the URL embedded in each task description, so the merge order and the review history stay coherent.

The MR-as-link pattern is the only cross-repository mechanism the agent needs. There is no shared state between the two workers, no message bus, no synchronisation step. Each agent treats its task as a single-repository task with one extra piece of context provided through the URL. The complexity of multi-repository coordination stays where it belongs, in the human breakdown of work.

The CLAUDE.md split

Each repository has its own `CLAUDE.md`. The backend one describes the NestJS module structure, the MikroORM conventions, the test setup with testcontainers, the BullMQ patterns, and the lint rules. The frontend one describes the Feature-Sliced Design layout, the Jotai + TanStack Query data layer, the MUI theming conventions, and the i18next requirements. Neither file needs to know about the other repository, with one important exception: both files include a short section called "Working from a linked MR" that tells the agent what to do when the task description contains a URL pointing at a merge request in the sibling repository.

That section is short. It says: if the task description contains a GitHub merge request URL, treat the contract excerpt the human pasted into the task description (request shape, response shape, error codes) as the source of truth for this task; the URL itself is for the human reviewer to follow, not for the agent to fetch. If the excerpt is missing or contradicts the surrounding description, return the task to the Incomplete column with a specific request for the missing piece, rather than guessing or trying to call out to another repository. The agent's PAT does not have access to the sibling repository — that is a deliberate scope choice in the security model — so all cross-repository information has to be funnelled through the task description.

Grafana MCP for log-based bug investigation

The backend project enables a Grafana MCP server on the worker so the agent can investigate production bugs from logs without leaving its sandbox. The Grafana MCP server (the upstream `mcp/grafana` image is the reference implementation, but any conforming server works) is configured in the `acme/api` repository under `.claude/mcp.json`:

```

{

 "mcpServers": {

   "grafana": {

     "command": "docker",

     "args": [

       "run", "--rm", "-i",

       "--env", "GRAFANA_URL",

       "--env", "GRAFANA_API_KEY",

       "mcp/grafana:latest",

       "--datasources", "loki-prod-readonly,prometheus-prod-readonly",

       "--allowed-folders", "Production - API,Production - Workers",

       "--query-timeout", "30s",

       "--max-rows", "1000"

     ],

     "env": {

       "GRAFANA_URL": "https://grafana.acme.internal",

       "GRAFANA_API_KEY": "${GRAFANA_API_KEY}"

     }

   }

 }

}

```

The Grafana API key is generated through Grafana's UI as a Viewer-role service account token, scoped to a dedicated "shipifyai-worker" service account that has read-only access to the two named datasources and the two named dashboard folders, and to nothing else. The key lives in the worker's env file as `GRAFANA_API_KEY` alongside `SHIPIFYAI_TOKEN`; it is not stored in the ShipifyAI backend because Grafana is an internal system the dashboard does not need to know about. The agent can therefore query Loki for log lines, ask Prometheus for time-series, and read the dashboards humans use during incidents, but cannot create alerts, edit dashboards, write to any datasource, or reach Grafana administrative endpoints.

The two narrowing flags on the command line matter. The `--datasources` allowlist prevents the agent from accidentally querying a datasource that points at a write-capable system; even though the API key is read-only, refusing to expose unrelated datasources through the MCP tool surface is a cheap second layer. The `--query-timeout` and `--max-rows` caps prevent a malformed query from holding a Grafana worker open or pulling a multi-gigabyte log dump into Claude's context. The CLAUDE.md instructs the agent to start narrow — by time window and by request id — and to widen the query only if the first one returns nothing useful.

A `bug-investigation` skill in `.claude/skills/bug-investigation.md` describes the procedure. It instructs the agent to extract from the task description the symptom, the time window, and any user, request, or order identifier the reporter provided. It tells the agent to query Loki first for log lines matching the identifier in that time window, then to look at the traces that contain the identifier, then to query Prometheus for relevant metrics over the same window. It enforces a definition of done that includes a written summary of what the logs show, the file and line number of the suspected root cause, a proposed fix, and a test that reproduces the bug at the unit or integration level. If the investigation is inconclusive — the logs do not contain the identifier, or the symptom does not match anything in the time window — the skill instructs the agent to return the task to the Incomplete column with a detailed explanation rather than to guess.

The skill explicitly forbids the agent from acting on the logs beyond writing the fix. It cannot post the contents of log lines into Jira comments, because logs may contain user data subject to data-protection requirements. It can quote line numbers, error messages, and stack traces in the MR description, but it must redact identifiers and free-text user inputs. The CLAUDE.md backs this up in its security section.

What the frontend project gets

The frontend project does not enable a Grafana MCP. There is little benefit and a clear cost — the frontend agent does not have a useful query to run against production logs that the backend agent could not run better, and adding a credential surface for no gain is the kind of choice the security article warns against. The frontend project enables only the filesystem MCP scoped to its own working clone. It has no cross-repository capability of any kind; the security model treats the two repositories as separate trust zones and does not let either agent reach into the other.

The frontend agent receives the bug investigation result entirely through the linked task. When the backend MR documents that the API now returns a `422` with `code: "ORDER_LINE_LIMIT_EXCEEDED"` for the scenario that triggered the bug, the human triager creating the follow-up frontend task copies the relevant contract excerpt into the new task description, pastes the backend MR URL alongside it as provenance for the reviewer, and moves the task to "Ready for AI (frontend)". The frontend agent reads the description, sees the contract, writes the matching `useMutation` error branch, the i18n entry for the user-facing message, and the test that asserts the message renders. Two agents, two repositories, one coherent fix — coordinated through a human hand-off step rather than through cross-repository token scope.

Where to go next

The worker configuration article covers how each of the two projects is registered in the dashboard. The Claude configuration article covers MCP servers in general, including the warnings about which MCP servers should never be connected to a worker (Jira, GitHub for task management, anything write-capable on a production system). The security article covers the four pillars that keep the two-project setup safe.