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 β
| Service | Main | Worktree 1 | Worktree 2 |
|---|---|---|---|
| Backend API | 3888 | 3890 | 3892 |
| Frontend | 5173 | 5175 | 5177 |
| Admin | 5174 | 5176 | 5178 |
| MCP server | 3889 | 3891 | 3893 |
| Mobile Metro | 8081 | 8082 | 8083 |
MongoDB (with --db) | 27017 | 27117 | 27118 |
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 scripts/create-worktree.sh <branch> [base-ref] [--db shared|empty|copy]This will:
- Create a git worktree at
../worktree/tell-ia-<name>on the branch (reusing it if it already exists, local or remote) - Write each app's
.env.localwith the worktree's ports, and regeneratefrpc.tomlwith unique subdomains - Start an isolated Mongo and write
.env.compose(only with--db empty|copy) - 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 - Add the folder to
../tell-ia.code-workspace - Run
pnpm install(the worktree gets its ownnode_modules) - 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.
| Mode | Behaviour |
|---|---|
shared | Default. Main's running Mongo, same data. |
empty | Isolated, empty Mongo on its own port. |
copy | Isolated, 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:
| App | Written |
|---|---|
agri-backend | PORT, FRONTEND_URL, CORS_ORIGINS (+ MONGODB_URI when isolated) |
agri-frontend | VITE_DEV_PORT, VITE_API_URL, VITE_CONVERSATIONAL_AI_SERVER_HOST |
agri-admin | VITE_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:
cd apps/agri-backend && docker compose up -dThen, from the worktree, each in its own terminal:
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 startCheck what it was given:
grep -hE '^(PORT|VITE_DEV_PORT|RCT_METRO_PORT)=' ../worktree/tell-ia-<name>/apps/*/.env.localConnect to the database β
Use MongoDB Compass with the connection string for the DB you want:
| DB | Connection 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}:
| Proxy | URL |
|---|---|
| Frontend | https://yourname-eng-123.dev.review-time.info |
| API | https://yourname-eng-123-api.dev.review-time.info |
| Admin | https://yourname-eng-123-admin.dev.review-time.info |
Start it from the worktree:
cd ../worktree/tell-ia-eng-123
frpc -c frpc.tomlThe dev name defaults to $USER; override it at creation with DEV_NAME:
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:
cd ../worktree/tell-ia-<name>
git switch some-other-branchWARNING
Two worktrees can't have the same branch checked out at once.
Remove a worktree β
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.