Isolated Dev Sessions
bun run dev gives you one dev environment pointed at one Convex deployment. That is
fine until you want two branches alive at the same time — a feature you are building
and a bug you are reproducing, or three agents working in parallel. Both convex dev
processes would push schema and functions to the same deployment, and whichever
pushed last wins.
bun run worktree:up solves that. It turns a git worktree into a self-contained dev
session: its own local Convex backend, its own database, its own Vite port, its own
seeded org and accounts. Sessions never see each other.
Create a session
Section titled “Create a session”bun run worktree:up my-featureThat creates the branch my-feature, places its worktree under
~/.worktrees/<repo>/my-feature, installs dependencies, boots a local Convex backend,
seeds accounts and an org, and starts Vite. It finishes by printing everything you need:
Session ready Path: /home/you/.worktrees/red-boilerplate/my-feature Branch: my-feature App: http://localhost:5207 Convex: http://127.0.0.1:3210 (anonymous:anonymous-agent) Credentials: /home/you/.worktrees/red-boilerplate/my-feature/.claude/rules/playwright.local.mdOpen the app URL and sign in with the credentials from the seed output. Run it again from inside an existing worktree — with no branch name — to restart that session.
Stop a session
Section titled “Stop a session”bun run worktree:downThis stops Vite and Convex but keeps the data. The next worktree:up comes back on
the same ports with the same org, the same users, and the same passwords.
To throw the data away and provision a clean backend:
bun run worktree:up --resetTo throw the whole session away, remove the worktree: git worktree remove <path>.
Shared secrets
Section titled “Shared secrets”Per-session secrets — BETTER_AUTH_SECRET and the ES256 API-key JWT pair — are minted
by the script and differ per session, which is what keeps sessions from sharing sign-ins.
Secrets that are genuinely shared (AI gateway, billing, email) live in one file outside
every worktree, so git clean -xfd cannot reach it and one copy serves every session:
mkdir -p ~/.redcp scripts/worktree-secrets.example.sh ~/.red/dev.envEvery key in it is optional, and so is the file. A missing key degrades exactly one
service — no AI runs without AI_GATEWAY_API_KEY, no billing without
AUTUMN_SECRET_KEY, no outbound email without RESEND_API_KEY. Auth, seeding, and the
app itself work regardless. Rotating a key here reaches every session on its next
worktree:up.
Set RED_DEV_ENV to keep several sets side by side:
RED_DEV_ENV=~/.red/dev-scratch.env bun run worktree:upReaching a session from another device
Section titled “Reaching a session from another device”Set DEV_HOST and DEV_SERVE=1 in the secrets file to put
tailscale serve in front of a session:
DEV_HOST=my-laptop.tailnet-name.ts.netDEV_SERVE=1TLS is the point, not convenience. Better Auth runs with useSecureCookies, and a plain
http:// origin on a tailnet name is not a secure context (only localhost is exempt) —
the browser would discard the session cookie and crypto.subtle would read as
undefined, so sign-in could never complete. tailscale serve terminates TLS with a
real certificate and proxies to loopback, so Vite stays bound to localhost and you open
no firewall holes.
DEV_HOST without DEV_SERVE binds every interface in the clear instead, and needs the
ports opened by hand.
Using a cloud deployment instead
Section titled “Using a cloud deployment instead”bun run worktree:up --cloudProvisions a named cloud dev deployment (dev/<branch>, expiring in 7 days) instead of a
local backend. Use it when the session needs a public *.convex.site URL — inbound
webhooks, OAuth callbacks, an MCP client reaching in from outside your machine. It
requires the main checkout to already have a Convex project configured.
How the isolation works
Section titled “How the isolation works”The Convex CLI keys local deployments on the project directory, so each worktree
automatically gets its own state directory (<worktree>/.convex/local/default), its own
sqlite database, its own admin key, and its own port pair.
The load-bearing step is that worktree-up.sh writes a clean root local env file.
Copying the main checkout’s would carry CONVEX_DEPLOYMENT=dev:<name>, and every
worktree’s convex dev would push to that one shared deployment — exactly the collision
the script exists to prevent.
Vite ports come from a hash of the branch slug in the range 5200–5229, so a session keeps
a stable URL across restarts and never collides with the bun run dev default of 5173.
Convex port pairs are checked against every sibling worktree’s saved claim, not just
against what is currently listening — a stopped session still owns its ports, and a
sibling that grabbed them would silently attach to the wrong backend.
Node version
Section titled “Node version”A local Convex backend compiles "use node" actions with the host’s Node, and Convex
builds them only for Node 20, 22, or 24. RED ships none, so this never comes up out of
the box. Once you add one, worktree:up checks the host’s Node and switches to a
supported version through nvm or mise if one is installed — or tells you to install
one, or to use --cloud, which builds node actions server-side.