You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Working on Supacode means running an in-development build against your real ~/.supacode and your actual worktrees. There's no isolation story: the dev build and your daily-driver install share the bundle id, the data directory, and every OS registration. I audited the codebase for what concretely goes wrong when two production instances run side by side (the only way to dogfood a change today without replacing your working install) — the failure modes are structural:
Terminal sessions can kill each other. Both instances restore the same layouts.json, so they derive identical supa-<uuid> zmx session names (ZmxSessionID.make) and attach to the same sessions — zmx broadcasts PTY output to all attached clients, so two windows silently share one shell. Closing a surface (or quitting with terminate-sessions) runs zmx kill unconditionally (killZmxSessions), destroying the other instance's live shell mid-command. This matches long-standing flakiness I've hit with two instances open.
All persisted state is last-writer-wins. The code says it outright — LayoutsIncrementalWriter.swift: "a second Supacode instance writing the same file concurrently is a dev-only scenario and accepted as last-writer-wins." The same whole-file pattern (atomic write, no lock, no re-read subscription) covers settings.json, sidebar.json, and repo-local supacode.json: a change made in the staler instance silently reverts the other's settings, pins, archives, and scripts. Both instances also share one UserDefaults domain, so live toggles bleed between them (sidebar nesting/grouping, remembered font size, palette recency, onboarding dismissals).
OS-level routing picks one instance.supacode:// deeplinks — CLI actions, notification clicks, browser links — go to whichever instance LaunchServices chooses; the wrong one fails or acts in the wrong window. Each process starts its own Sparkle updater with auto-install, so one instance can replace the bundle the other is still executing from. And outside a Supacode terminal, every CLI command fails with "Multiple Supacode instances found" (Dispatcher.resolveSocket).
Worktree lifecycle races. Delete in instance A runs git worktree remove --force --force with no cross-process ownership check — ripping the cwd out from under B's live shells and scripts. Lifecycle guards and running-script registries are per-process, so a non-idempotent archive/delete script can run twice concurrently. Worktree creation is check-then-create (name collisions), and doubled HEAD watchers and PR polling double git churn and GitHub API burn.
It also blocks two-ended (remote) feature work. Anything remote — a binary proxying notifications back, an event stream, an SSH-side helper — is a protocol with the app on one end. Today the in-dev handlers must terminate in the app you daily-drive: a buggy handler pollutes your real notification stream, protocol migrations land mid-flight in prod, a crash loop takes down the app your agents run in. A dev build pairs the dev remote binary with the dev app end-to-end while prod stays on the released protocol — and makes version-skew testing runnable, since both pairs can be live at once.
In short: the codebase (reasonably!) assumes one instance per bundle id and data dir. That's exactly what makes dogfooding in-flight changes risky — today's safest workflow is "quit the app you use all day and hope nothing corrupts."
Proposed solution
A parallel "Supacode Dev" app that makes the isolation assumption true by construction, rather than adding cross-process locking for a scenario production users never hit:
Own bundle id (app.supabit.supacode.dev), name/icon, and supacode-dev:// scheme → separate UserDefaults domain, separate LaunchServices/notification/deeplink identity
State under ~/.supacode-dev (settings, sidebar, layouts, worktree clones) → separate persisted files, and separately-restored surface UUIDs mean no shared zmx sessions
Sparkle disabled → the dev build can't be replaced mid-session or self-update into a release build
Socket-dir namespacing: the dev app binds its CLI socket under /tmp/supacode-dev-<uid> (same runtime dev-build check), each bundled CLI resolving only its own directory — so supacode keeps working with both apps running, and each build's CLI always reaches its own app. No separate CLI product needed: the CLI is bundled (each app ships its matching copy) and in-terminal routing is already pinned by SUPACODE_SOCKET_PATH.
Make targets for the loop: make run-app-dev, and make dev (watch, rebuild, relaunch)
The prod target stays byte-for-byte unchanged. A working implementation exists in draft PR #510 (opened before the contribution policy landed — I'll bring it into compliance and rebase if approved); happy to rework the approach based on direction here.
Alternatives considered
Backing up / restoring ~/.supacode by hand: error-prone, and doesn't address zmx session sharing, deeplink routing, defaults-domain bleed, or auto-update.
Real cross-process coordination (flock/NSFileCoordinator, socket arbitration, zmx client counting): a much larger, riskier change benefiting only a scenario end users don't hit — isolation makes the whole class unreachable instead.
An env-var override for the data directory only: fixes file clobbering but not the shared bundle id (defaults domain, deeplinks, notifications, Sparkle, LaunchServices).
A build configuration instead of a dedicated target: tried in Add isolated dev build (make run-app-dev) #510 — breaks supacode-cli (Tuist only generates SPM projects with Debug/Release) and a PRODUCT_NAME override collides the shared frameworks.
Supacode version
0.10.4
Are you planning to build this yourself?
I intend to open a pull request once this is approved and marked ready.
Before submitting
I searched existing issues and this is not a duplicate.
I understand feature pull requests are closed until the issue is marked ready.
What problem does this solve?
Working on Supacode means running an in-development build against your real
~/.supacodeand your actual worktrees. There's no isolation story: the dev build and your daily-driver install share the bundle id, the data directory, and every OS registration. I audited the codebase for what concretely goes wrong when two production instances run side by side (the only way to dogfood a change today without replacing your working install) — the failure modes are structural:Terminal sessions can kill each other. Both instances restore the same
layouts.json, so they derive identicalsupa-<uuid>zmx session names (ZmxSessionID.make) and attach to the same sessions — zmx broadcasts PTY output to all attached clients, so two windows silently share one shell. Closing a surface (or quitting with terminate-sessions) runszmx killunconditionally (killZmxSessions), destroying the other instance's live shell mid-command. This matches long-standing flakiness I've hit with two instances open.All persisted state is last-writer-wins. The code says it outright —
LayoutsIncrementalWriter.swift: "a second Supacode instance writing the same file concurrently is a dev-only scenario and accepted as last-writer-wins." The same whole-file pattern (atomic write, no lock, no re-read subscription) coverssettings.json,sidebar.json, and repo-localsupacode.json: a change made in the staler instance silently reverts the other's settings, pins, archives, and scripts. Both instances also share one UserDefaults domain, so live toggles bleed between them (sidebar nesting/grouping, remembered font size, palette recency, onboarding dismissals).OS-level routing picks one instance.
supacode://deeplinks — CLI actions, notification clicks, browser links — go to whichever instance LaunchServices chooses; the wrong one fails or acts in the wrong window. Each process starts its own Sparkle updater with auto-install, so one instance can replace the bundle the other is still executing from. And outside a Supacode terminal, every CLI command fails with "Multiple Supacode instances found" (Dispatcher.resolveSocket).Worktree lifecycle races. Delete in instance A runs
git worktree remove --force --forcewith no cross-process ownership check — ripping the cwd out from under B's live shells and scripts. Lifecycle guards and running-script registries are per-process, so a non-idempotent archive/delete script can run twice concurrently. Worktree creation is check-then-create (name collisions), and doubled HEAD watchers and PR polling double git churn and GitHub API burn.It also blocks two-ended (remote) feature work. Anything remote — a binary proxying notifications back, an event stream, an SSH-side helper — is a protocol with the app on one end. Today the in-dev handlers must terminate in the app you daily-drive: a buggy handler pollutes your real notification stream, protocol migrations land mid-flight in prod, a crash loop takes down the app your agents run in. A dev build pairs the dev remote binary with the dev app end-to-end while prod stays on the released protocol — and makes version-skew testing runnable, since both pairs can be live at once.
In short: the codebase (reasonably!) assumes one instance per bundle id and data dir. That's exactly what makes dogfooding in-flight changes risky — today's safest workflow is "quit the app you use all day and hope nothing corrupts."
Proposed solution
A parallel "Supacode Dev" app that makes the isolation assumption true by construction, rather than adding cross-process locking for a scenario production users never hit:
app.supabit.supacode.dev), name/icon, andsupacode-dev://scheme → separate UserDefaults domain, separate LaunchServices/notification/deeplink identity~/.supacode-dev(settings, sidebar, layouts, worktree clones) → separate persisted files, and separately-restored surface UUIDs mean no shared zmx sessions/tmp/supacode-dev-<uid>(same runtime dev-build check), each bundled CLI resolving only its own directory — sosupacodekeeps working with both apps running, and each build's CLI always reaches its own app. No separate CLI product needed: the CLI is bundled (each app ships its matching copy) and in-terminal routing is already pinned bySUPACODE_SOCKET_PATH.make run-app-dev, andmake dev(watch, rebuild, relaunch)The prod target stays byte-for-byte unchanged. A working implementation exists in draft PR #510 (opened before the contribution policy landed — I'll bring it into compliance and rebase if approved); happy to rework the approach based on direction here.
Alternatives considered
~/.supacodeby hand: error-prone, and doesn't address zmx session sharing, deeplink routing, defaults-domain bleed, or auto-update.supacode-cli(Tuist only generates SPM projects with Debug/Release) and aPRODUCT_NAMEoverride collides the shared frameworks.Supacode version
0.10.4
Are you planning to build this yourself?
ready.Before submitting
ready.