Skip to content

feat(desktop): isolate dev profiles per worktree so several can run at once - #3124

Merged
benjaminshafii merged 1 commit into
devfrom
feat/dev-instance-isolation
Jul 25, 2026
Merged

feat(desktop): isolate dev profiles per worktree so several can run at once#3124
benjaminshafii merged 1 commit into
devfrom
feat/dev-instance-isolation

Conversation

@benjaminshafii

Copy link
Copy Markdown
Member

Problem

Contributors keep many git worktrees of this repo. Every dev Electron instance shares one profile directory (com.differentai.openwork.dev), so app.requestSingleInstanceLock() fails for the second worktree. The failure mode is the bad part: Chromium has already opened the CDP port by then, so the process lingers answering /json/version while never creating a window and never logging anything. It looks exactly like a hang, with no hint and no easy way to run two worktrees side by side.

The primitive

OPENWORK_DEV_PROFILE — dev-only, completely inert when packaged:

value behavior
unset exactly today's behavior — keeps the shared profile
auto stable per-worktree profile derived from the app root path
any name explicitly named profile, sanitized

Precedence (documented in the code): OPENWORK_ELECTRON_USERDATAOPENWORK_ELECTRON_APP_IDENTIFIEROPENWORK_DEV_PROFILE → today's default.

Unset is unchanged on purpose. Nobody's existing dev profile (sessions, auth, providers) moves or disappears. There is no migration.

Fail loud instead of hanging. On lock contention a dev instance now names the locked profile directory, says another instance holds it, gives the exact remedy, and exits for real (app.exit + process.exit) so the CDP port is released and no zombie remains. second-instance focus behavior is untouched, and production still uses plain app.quit().

Dev boot banner so you can tell instances apart:

[openwork] dev profile=…/com.differentai.openwork.dev.cap-dev-profile-wt2-156b2b1bc8 cdp=http://127.0.0.1:9853

pnpm dev:worktreeOPENWORK_DEV_PROFILE=auto plus free CDP and Vite ports. pnpm dev keeps its pinned 9823 unchanged, because evals/runner default-probes 9825 then 9823 and moving it would break eval discovery.

It also defaults OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN=1, which I only found by testing: a brand-new profile has no stored credentials, so on macOS the real keychain prompts as soon as Chromium persists an authenticated cookie — and main.mjs already documents that this modal "blocks the entire Electron main loop". Without this default, the new script would reproduce the very hang it exists to remove. Override with =0.

Verification

pnpm typecheck                        clean
pnpm --filter @openwork/desktop test  116 tests, 115 pass, 0 fail, 1 skipped

New apps/desktop/electron/dev-profile.test.mjs covers: unset → legacy identifier unchanged; auto stable for the same path and different for another; named value sanitized; OPENWORK_ELECTRON_USERDATA beating OPENWORK_DEV_PROFILE; packaged mode ignoring the var.

Observed live from a second worktree:

  • isolated profile actually produced — …/com.differentai.openwork.dev.cap-dev-profile-wt2-156b2b1bc8
  • free Vite port actually selected and bound — [electron-dev] Vite dev server will use free port 59148
  • free CDP port selected, banner correct
  • pnpm dev script verified byte-identical to before

What I could NOT verify, honestly

I could not observe two dev windows side by side on this machine, because dev Electron currently opens no window on this macOS host at all — I reproduced 0 CDP page targets from the unmodified main checkout with an isolated profile and a mock keychain, so it is pre-existing and unrelated to this change. The same commit does open a window in a clean Linux sandbox.

I also want to correct something I claimed earlier on a related PR: I attributed that local no-window hang to the single-instance lock. That was wrong — this testing disproved it. The lock collision is real and worth fixing (this PR), but it is not the cause of the local no-window symptom, which remains an open macOS-host issue I have not root-caused.

So: the profile/port collision and the silent-hang-on-lock-contention are fixed and unit-tested; the "two windows at once" end state is verified only in the sense that both instances get distinct profiles and ports.

Every dev Electron instance shared one profile directory, so
app.requestSingleInstanceLock() failed for the second worktree. Chromium had
already opened the CDP port by then, so the process lingered answering
/json/version without ever creating a window or logging anything - it looked
exactly like a hang.

Add OPENWORK_DEV_PROFILE (dev-only, inert when packaged): unset keeps today's
shared profile so existing profiles are untouched, 'auto' derives a stable name
from the worktree path, and any other value names a profile explicitly.
OPENWORK_ELECTRON_USERDATA still wins.

On lock contention the second dev instance now explains which profile directory
is held and how to isolate, then exits for real so the CDP port is released.
Dev startup prints the resolved profile directory and CDP address.

Add pnpm dev:worktree for the common case: auto profile plus free CDP and Vite
ports, so several worktrees run side by side.
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview, Comment Jul 25, 2026 3:54pm
openwork-den Ready Ready Preview, Comment Jul 25, 2026 3:54pm
openwork-den-worker-proxy Ready Ready Preview, Comment Jul 25, 2026 3:54pm
openwork-diagnostics Ready Ready Preview, Comment Jul 25, 2026 3:54pm
openwork-landing Ready Ready Preview, Comment, Open in v0 Jul 25, 2026 3:54pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant