Skip to content

Parallel Development (Worktrees) ​

Run several copies of the project simultaneously β€” useful when you review a PR without stopping your own dev server, keep a long migration and a quick fix apart, or let parallel agents work on different branches without port conflicts or shared database interference.

Each worktree gets:

  • Its own git worktree (a real checkout of another branch, sharing one .git)
  • Its own ports, auto-assigned to the next free one
  • Optionally its own MongoDB, empty or seeded from your shared DB

Ports ​

ServiceMainWorktree 1Worktree 2
Backend API388838903892
Frontend517351755177
Admin517451765178
MCP server388938913893
Mobile Metro808180828083
MongoDB (with --db)270172711727118

Each worktree takes the next free port upward from each base, so the exact numbers depend on what is already running. They are written to its env files, so concurrent runs never collide.

Create a worktree ​

From the main repo root:

bash
bash scripts/create-worktree.sh <branch> [base-ref] [--db shared|empty|copy]

This will:

  1. Create a git worktree at ../worktree/tell-ia-<name> on the branch (reusing it if it already exists, local or remote)
  2. Write each app's .env.local with the worktree's ports, and regenerate frpc.toml with unique subdomains
  3. Start an isolated Mongo and write .env.compose (only with --db empty|copy)
  4. Seed it from the shared DB (only with --db copy) β€” this one runs last, once the rest is done, so a failed seed still leaves a usable worktree
  5. Add the folder to ../tell-ia.code-workspace
  6. Run pnpm install (the worktree gets its own node_modules)
  7. Build the shared packages (@tellia-solutions/schemas)

Database ​

Shared by default β€” your data and logins are already there. Use --db when the work would damage them.

ModeBehaviour
sharedDefault. Main's running Mongo, same data.
emptyIsolated, empty Mongo on its own port.
copyIsolated, seeded from the shared DB (host-side mongodump, --noIndexRestore).

Mongo only β€” Redis and assets stay shared. copy needs the shared DB running to read from; if it isn't, the worktree is still created with an empty DB and the script prints the reseed command. Atlas $search indexes aren't copied.

From Claude Code ​

/worktree <ENG-id | PR-id | branch-name>

The skill resolves an ENG-### or PR id to its branch, then runs the same script. Install it with cp -r apps/docs/skills/worktree .claude/skills/.

Ports and secrets ​

Every .env is generated β€” env-sync.sh resolves the tracked .env.tpl with op inject and appends the gitignored .env.local, which wins. So the script never copies a .env; it writes the worktree's values to apps/<app>/.env.local:

AppWritten
agri-backendPORT, FRONTEND_URL, CORS_ORIGINS (+ MONGODB_URI when isolated)
agri-frontendVITE_DEV_PORT, VITE_API_URL, VITE_CONVERSATIONAL_AI_SERVER_HOST
agri-adminVITE_DEV_PORT, VITE_API_URL

The first dev of each app generates its .env (one 1Password approval). agri-mcp reads only .env, so it gets both files; mobile has no template and keeps a patched .env. Never patch .env.tpl β€” it's tracked.

Run it ​

The apps need the database running β€” the shared one from the main repo, unless the worktree has its own:

bash
cd apps/agri-backend && docker compose up -d

Then, from the worktree, each in its own terminal:

bash
pnpm --filter agri-backend dev
pnpm --filter agri-frontend dev
pnpm --filter agri-admin dev
pnpm --filter @tellia-solutions/mcp dev
pnpm --filter @tellia-solutions/mobile-app start

Check what it was given:

bash
grep -hE '^(PORT|VITE_DEV_PORT|RCT_METRO_PORT)=' ../worktree/tell-ia-<name>/apps/*/.env.local

Connect to the database ​

Use MongoDB Compass with the connection string for the DB you want:

DBConnection String
Main (shared)mongodb://user:pass@localhost:27017/?authSource=admin&directConnection=true
Worktree 1 (--db)mongodb://user:pass@localhost:27117/?authSource=admin&directConnection=true
Worktree 2 (--db)mongodb://user:pass@localhost:27118/?authSource=admin&directConnection=true

A shared worktree has no DB of its own β€” it is the main one on 27017. For an isolated one, its port is in apps/agri-backend/.env.compose.

Dev tunnels ​

Each worktree gets a pre-configured frpc.toml with proxies for the frontend, API and admin, on its own ports. Subdomains follow {user}-{worktree}:

ProxyURL
Frontendhttps://yourname-eng-123.dev.review-time.info
APIhttps://yourname-eng-123-api.dev.review-time.info
Adminhttps://yourname-eng-123-admin.dev.review-time.info

Start it from the worktree:

bash
cd ../worktree/tell-ia-eng-123
frpc -c frpc.toml

The dev name defaults to $USER; override it at creation with DEV_NAME:

bash
DEV_NAME=alex bash scripts/create-worktree.sh <branch>

See Dev Tunnels for frpc install and FRP_TOKEN.

Switch branches ​

A worktree is a regular checkout:

bash
cd ../worktree/tell-ia-<name>
git switch some-other-branch

WARNING

Two worktrees can't have the same branch checked out at once.

Remove a worktree ​

bash
bash scripts/remove-worktree.sh <name> [--force] [--keep-branch]

Removes the directory, prunes the workspace entry, deletes the branch, and tears down an isolated Mongo (container + volumes) first.

TIP

--keep-branch keeps the local branch (it is deleted otherwise). --force if the worktree has uncommitted changes. The remote branch is never touched either way.


Replaces the old create-instance.sh / remove-instance.sh flow, which fixed-offset every port and copied an apps/agri-backend/.env that no longer exists.