What problem does this solve?
The distinct-bundle instances (prime, Convercode 2, 3, …) all point at the same repositories, so the same worktree can be opened in more than one instance at once. A worktree is single-tenant state, and today nothing separates two release instances driving it.
The damage is concrete, and the codebase already documents the mechanism — in the comment justifying the Debug-only ZMX_DIR pin (CON-1105):
A dev instance must not be able to attach the Release app's live sessions: session ids come from layouts.json, so an identical socket dir means two apps driving one shell.
Between two release instances that pin is absent, so:
- One shell, two clients. zmx session ids are
"supa-" + surfaceUUID, and those UUIDs come from the shared layouts.json. Two instances restoring the same worktree compute identical session names against the same socket dir. zmx treats concurrent clients as first-class, so it is not an error — output is broadcast to both, either window's keystrokes steal leadership, and only the leader's TIOCSWINSZ applies, so two differently-sized windows fight over the pty geometry on every keypress.
- Two writers on one layout key.
LayoutsIncrementalWriter documents its own limit ("accepted as last-writer-wins"). For the same worktree the loser's whole tab tree is discarded — and applicationWillTerminate writes every live key, so the second instance quitting rewrites the first one's live worktree.
- Conflicting git operations. One checkout, one index, one
HEAD, two apps running branch renames, stashes, and setup scripts.
- Stale sidebar rosters. Neither instance sees the other's worktree lifecycle mutations.
None of it is surfaced. There is no warning and no way to discover which instance already has the worktree.
Proposed solution
Claim a worktree for the instance that has its session, and steer rather than duplicate.
- The instance that opens a worktree's session takes an exclusive
flock on a claim file under /tmp/supacode-$UID/worktree-claims/, recording its instance number, bundle id, and pid.
- A second instance that fails the lock does not create a terminal state. Its detail pane says "Already open in Convercode 2" and offers Switch to Convercode 2 (activates that instance and reveals the worktree there), Try Again, and Open Anyway behind a confirmation that names the consequence.
- A claim whose owner is gone never blocks. Holding the lock for the session's lifetime means the kernel releases it on process death by any means — quit, crash, force-quit,
SIGKILL — so there is no staleness heuristic, no pid-reuse hazard, and no release-on-quit path to forget.
- Same-instance reopen is silent; the claim releases when the worktree's last tab closes and when its state is torn down.
- Debug and Release get separate claim namespaces, matching the boundary CON-1105 already drew — otherwise a dev build could never open a worktree the installed app has open.
Alternatives considered
- A pid-stamped lock file plus a
kill(pid, 0) staleness sweep. Correct only if the sweep is correct: it inherits pid reuse (the owner dies, the OS reissues its pid, the sweep reports "still alive" forever) and needs an explicit release on every teardown branch, including the ones that do not run on a crash.
- Querying each running instance's control socket ("do you have worktree X open?"). Liveness is implicit, but it is an oracle, not a mutual-exclusion primitive — two instances querying simultaneously both see "unowned" and both open. Closing that needs a tie-break protocol to re-derive what one
flock gives atomically.
- Mixing the instance number into the zmx session id. Silently breaks resume for every already-persisted session, and addresses only the zmx third of the problem.
- Per-instance state roots. Dissolves the collision instead of guarding it, but splits the sidebar roster, settings, and repository list per instance — the opposite of what multi-instance is for.
Linear: CON-1172
Supacode version
0.14.0
What problem does this solve?
The distinct-bundle instances (prime, Convercode 2, 3, …) all point at the same repositories, so the same worktree can be opened in more than one instance at once. A worktree is single-tenant state, and today nothing separates two release instances driving it.
The damage is concrete, and the codebase already documents the mechanism — in the comment justifying the Debug-only
ZMX_DIRpin (CON-1105):Between two release instances that pin is absent, so:
"supa-" + surfaceUUID, and those UUIDs come from the sharedlayouts.json. Two instances restoring the same worktree compute identical session names against the same socket dir. zmx treats concurrent clients as first-class, so it is not an error — output is broadcast to both, either window's keystrokes steal leadership, and only the leader'sTIOCSWINSZapplies, so two differently-sized windows fight over the pty geometry on every keypress.LayoutsIncrementalWriterdocuments its own limit ("accepted as last-writer-wins"). For the same worktree the loser's whole tab tree is discarded — andapplicationWillTerminatewrites every live key, so the second instance quitting rewrites the first one's live worktree.HEAD, two apps running branch renames, stashes, and setup scripts.None of it is surfaced. There is no warning and no way to discover which instance already has the worktree.
Proposed solution
Claim a worktree for the instance that has its session, and steer rather than duplicate.
flockon a claim file under/tmp/supacode-$UID/worktree-claims/, recording its instance number, bundle id, and pid.SIGKILL— so there is no staleness heuristic, no pid-reuse hazard, and no release-on-quit path to forget.Alternatives considered
kill(pid, 0)staleness sweep. Correct only if the sweep is correct: it inherits pid reuse (the owner dies, the OS reissues its pid, the sweep reports "still alive" forever) and needs an explicit release on every teardown branch, including the ones that do not run on a crash.flockgives atomically.Linear: CON-1172
Supacode version
0.14.0