Skip to content

Plural Electron: scope desktop dev to the worktree - #664

Merged
crs48 merged 6 commits into
mainfrom
claude/agentic-electron-dev-experience-74b20c
Jul 31, 2026
Merged

crs48 merged 6 commits into
mainfrom
claude/agentic-electron-dev-experience-74b20c

Conversation

@crs48

@crs48 crs48 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Implements exploration 0413 — 25/26 items.

The bug

Electron dev was a machine-wide singleton: one SingletonLock, one userData, hard-coded 5177/9223/4444/31415. This repo is worked across 87 git worktrees, 41 of them agent worktrees.

So the second pnpm dev lost the lock, called app.quit(), and exited 0. An agent read that as success, attached playwright-electron to :9223, and drove whichever instance already owned the port — usually a different branch. Every step succeeded; the verification was false. The documented mitigation ("check the profile name in the title bar") could not work: the title was plain xNet for the default profile, so the incumbent and the newcomer were identical in exactly the case worth catching.

The fix

  • Scope. Profile wt-<worktree> and a ten-port block (20000 + hash(path) % 500 * 10) derived from the linked worktree, resolved once in scripts/dev-launch.mjs and passed down as XNET_DEV_SCOPE. The main checkout is unchanged — default, legacy ports — so nothing pre-existing moves. Because Chromium keys the lock on userData, each worktree gets its own lock for free.
  • Identity. window.__xnetDev (worktree, branch, commit, profile, ports, pid), dev-only, read through the tool an agent already has. The window title names the profile and commit for every profile.
  • Observability. Main and data-process records reach the renderer console tagged [main]/[data], through a bounded ring buffer so failures from before the window existed survive — window.__xnetDev.logs().
  • Loud failures. strictPort on the renderer; lock loss → named error + exit(1); a CDP identity assertion that refuses to report ready when the port serves another tree's renderer.

Verified against a live collision

The main checkout's dev app was running throughout, so this is the real thing, not a simulation.

Instance Profile Renderer CDP Lock holder
main checkout (main) default 5177 9223 …local-32967
this worktree wt-agentic-…-74b20c 23400 23401 …local-16919

Both alive at once — the thing that was impossible before.

FATAL: profile "wt-…" is already running (lock held by Chriss-MacBook-Pro-3.local-16919)   # exit 1
FATAL: :9223 is serving http://localhost:5177/, not our renderer on :23450                 # refused ready
Error: Port 23400 is already in use                                                        # strictPort

Gates: pnpm test 11,426 passed / 1,121 files · turbo run typecheck 40/40 uncached · check:electron-parity OK.

Three defects found by building it

  • stableProfileOffset() could not be reused as the doc proposed — its numeric-suffix branch collapses every worktree slug ending in a hex digit onto the same offset. Worktrees use FNV-1a over the whole path.
  • Git hooks export GIT_DIR/GIT_WORK_TREE, which leak into git subprocesses and made a main-checkout commit resolve as a linked worktree. git() now scrubs GIT_*; regression test included.
  • BrowserWindow's title option is inert once the document declares <title> — the label is now owned via page-title-updated.

Not done

  • Recording turns-to-prototype (desktop vs web) — a measurement across future work, not a change this PR can contain.
  • rebuild-if-stale.mjs (0404) does not notice pnpm install --force replacing the native binary, so its stamp can stay warm over a wrong-ABI module. Flagged in the doc as a follow-up.

🤖 Generated with Claude Code

xNet Test added 5 commits July 31, 2026 06:48
0404 wired the desktop prototyping loop for a single seat. This documents
what is still asymmetric versus the web loop: the Electron dev environment is
a machine-wide singleton (one SingletonLock, one userData, fixed 5177/9223)
while the repo is worked across 87 git worktrees.

Recommends deriving the profile and a ten-port block from the worktree,
publishing provenance on window.__xnetDev, bridging main and data-process
logs into the renderer console, and making port/lock collisions loud.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
Electron dev was a machine-wide singleton — one SingletonLock, one userData,
hard-coded 5177/9223/4444/31415 — while the repo is worked across dozens of
git worktrees. The second `pnpm dev` lost the lock, called app.quit(), and
exited 0; an agent then attached to :9223 and drove whichever instance already
owned it, usually a different branch. Every step succeeded and the
verification was false.

- Derive profile `wt-<worktree>` and a ten-port block (20000 + offset*10) from
  the linked worktree, resolved once in the dev launcher and passed down as
  XNET_DEV_SCOPE. The main checkout keeps `default` and its legacy ports.
- Publish provenance on `window.__xnetDev` (worktree, branch, commit, ports,
  pid), dev-only, and name the instance in the title bar for every profile.
- Forward main and data-process logs into the renderer console through a
  bounded ring buffer, so a CDP-attached agent sees all three processes and
  boot failures survive until a window exists.
- Fail loudly: strictPort on the renderer, a named error plus exit(1) on lock
  loss, and a CDP identity assertion that refuses to report ready when the
  port is serving someone else's renderer.
- Add `dev:clean` to prune profiles whose worktree is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
The port tables in apps/electron/AGENTS.md and the electron-prototype skill
became wrong the moment ports started being derived per worktree, and the
skill's "confirm which instance you attached to — the profile name in the title
bar is the cheapest signal" was never followable: the title was plain `xNet`
for the `default` profile, so a stale instance and your own were identical in
exactly the case worth catching.

Replaces both with the derivation rule, the `[xnet-dev] ready {…}` line to wait
for, and `window.__xnetDev` as the authoritative answer to "which tree is
this?". Drops the hand-cut `electron-renderer-worktree (vite 5178)` launch
entry, which existed because there was only ever one worktree seat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
The `title` option on BrowserWindow is only the *initial* title — Electron
discards it as soon as the document declares its own `<title>`, and
renderer/index.html does. So the profile label added for 0413 never actually
reached the window: verified titles still read plain `xNet`.

Own the title in development via `page-title-updated`, which also survives the
renderer updating it later. `document.title` is deliberately left alone —
CanvasView reads it to name captured content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
…d desktop dev

25 of 26 items. Records the measured results against a live pair of instances —
the user's main-checkout app was running throughout, so the CDP collision the
exploration is about was reproduced and then caught rather than simulated.

Also notes three defects found by building it (stableProfileOffset could not be
reused; git hook variables corrupted worktree detection; the window title
option was inert) and one left alone (rebuild-if-stale's stamp does not notice
`pnpm install --force` replacing the native binary).

The remaining item — recording turns-to-prototype, desktop versus web — is a
measurement activity across future work, not a change this PR can contain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
@crs48
crs48 temporarily deployed to pr-664 July 31, 2026 14:34 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Preview removed for PR #664.

github-actions Bot added a commit that referenced this pull request Jul 31, 2026
The first version resolved against the running checkout, so it asserted a
property of the machine rather than of the code: it passed locally (a linked
worktree) and failed in CI (a plain clone), and it hard-coded absolute paths
from one developer's disk.

Build both shapes in a temp directory instead — a plain checkout and a real
`git worktree add` of it — so the same assertions hold anywhere git exists.
The setup helper scrubs GIT_* for the same reason the resolver does: under a
git hook those variables are exported and its `git commit` operated on the
hook's repository instead of the temp one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
@crs48
crs48 temporarily deployed to pr-664 July 31, 2026 14:48 — with GitHub Actions Inactive
@crs48
crs48 merged commit ba62758 into main Jul 31, 2026
19 checks passed
@crs48
crs48 deleted the claude/agentic-electron-dev-experience-74b20c branch July 31, 2026 15:31
github-actions Bot added a commit that referenced this pull request Jul 31, 2026

This branch was previously deployed

1 inactive deployment
pr-664 — a83e2829 Deployed Jul 31, 2026 by crs48 via deploy-preview #1305
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