fix(run): replace the running instance instead of refreshing it
Merged
DA
2mo ago+104+104+012345678901234567890123456789-2-2-0123456789
Summary
./scripts/run.shrebuilds the app and then launches Electron, but a running instance owns the single-instance lock, so the new process quits on startup and only nudges the old one — it re-reads display geometry and re-asserts the panel. The freshly built code never runs, which reads as the launcher doing nothing.- The script now stops the instance that holds the lock and waits for it to exit, because the lock is released as that process goes away rather than when it is signalled.
--keep-runningopts out and keeps the re-assert behaviour. - The holder is identified from Electron's own lock file (
SingletonLock, a symlink named<hostname>-<pid>) rather than by matching process names. That is exact, and it covers the case a name match would miss: the lock is keyed on the app name, so a Luke launched from any worktree blocks every other checkout, and that is the process a relaunch has to replace. A stale lock can name a pid that has since been reused, so a holder that is not an Electron app is reported instead of signalled. - No app code changes:
apps/is untouched.
Evidence
- Platform-independent checks:
./scripts/check.sh— passed (repository contract checks passed; biome checked 48 files, no fixes; 0 test failures; builds clean) - macOS Electron verification (
./scripts/verify.sh):not run— no app-code change, andverify.shpackages and screenshots the app without exercisingrun.sh, so it cannot cover this change. CI runs it.
Behaviour of the new helper was exercised against stand-in processes, covering every path:
| Case | Result |
|---|---|
| No lock file | no-op, exit 0 |
| Lock naming a dead pid (stale) | no-op, exit 0 — Electron reclaims it |
| Lock naming a live pid that is not Luke | refuses, exit 1, process left alone |
| Lock naming a live Electron main process | stopped, exit 0 |
Holder ignoring SIGTERM | escalates to SIGKILL after 5s, exit 0 |
Argument handling: --keep-running is consumed and every other argument is forwarded to Electron with order and quoting preserved.
The lookup was also dry-run (read-only) against a real Luke running on macOS: the app name resolved to @luke/desktop, SingletonLock parsed to a live pid, ps confirmed the Electron identity check matches, and the verdict was "would stop pid 3313". That instance belonged to a different Conductor workspace than the one launching — the case that motivated identifying the holder by lock file rather than by process path.
Automated visual evidence
Download the deterministic macOS evidence · workflow run
- Commit:
31701fae0bfa2d465b74a32a7fd49ca595d8b78d - Scenario:
smoke - Physical-notch check: not performed by CI
Physical-device evidence
- Screenshot or screen recording:
not attached— no UI change - Physical-notch check:
not performed— no window geometry or appearance change - Device/display configuration:
not recorded
Notes
- Not verified: a real launch on macOS end-to-end (stop the live instance, then start the new build). The sandbox is Linux, so the pieces were verified separately — lock resolution and the identity check against the real running instance, and stop/wait/escalate against stand-in processes.
- The old instance is stopped before
pnpm startbuilds, so a failing build leaves nothing running. Building first would keep the old panel up until the new one is ready, but means duplicating the desktopstartpipeline intorun.shor runningswiftctwice, sincebuild:nativehas no caching.
🤖 Generated with Claude Code