fix(desktop): start the terminal reaper in the desktop host-service entry
Summary
- Wire
startTerminalReaper(db)into the desktop host-service entry point. It was only called frompackages/host-service/src/serve.ts(the standalone/CLI entry), so the packaged desktop app never ran the terminal reaper at all. - This is the actual fix for "ports don't show up after an update until the workspace is selected" — and it also turns on the 5-minute orphan reap (#5168), which has likewise never run in the desktop app.
Why / Context
#5325 added the reaper-driven port-scan reconcile (register daemon sessions no renderer has attached, e.g. after a host-service restart) and #5430 added 2–90s warm-up retries. Both are correct — but startTerminalReaper was never invoked by apps/desktop/src/main/host-service/index.ts, the entry the desktop app actually spawns. In the shipped app the only portManager.upsertSession caller was the renderer terminal-attach path, so ports for surviving sessions stayed invisible until the user opened the workspace.
Confirmed against a production install (v1.13.0): the asar contains the #5430 code, the daemon adopts within seconds, valid terminal_sessions rows exist — and the host-service log contains zero reaper lines across every run.
Supersedes the revert stack in #5437 (kept as a draft safety net; merging that would not fix this symptom).
How It Works
startTerminalReaper is exported from the @superset/host-service root, and each serving entry (serve.ts and the desktop entry) calls it once in its listen callback — the same per-entry pattern as installProcessSafetyNet, initTerminalBaseEnv, and connectRelay. All reaper logic stays in the package.
Two single-call-site alternatives were tried and rejected during review discussion: starting it inside createApp requires a test-only escape hatch (the reaper is a destructive background actor that reaps integration tests' staged daemon sessions mid-test — 5 failures), and a startHostRuntime wrapper was pure pass-through indirection.
Manual QA
End-to-end against the freshly built dist/main/host-service.js (run under Electron-as-Node, exactly as the app spawns it):
- Started a scratch pty-daemon, opened one session running
python3 -m http.server 18923, and disconnected — an alive, unattached daemon session, the post-update state. - Started the built host-service against a scratch DB with
SUPERSET_PTY_DAEMON_SOCKETpointed at the scratch daemon, and inserted the session'sterminal_sessionsrow (status: "active"+ workspace id). - Warm-up sync logged
port-scan sync: registered 1 unattached daemon session(s) for scanningwithin seconds — no renderer ever attached. ports.getAllfor that workspace returned{ port: 18923, processName: "Python", ... }.- Verified the built bundle contains the
startTerminalReaper(db)call. - Re-ran the scratch harness against the restructured bundle (reaper started from
createApp): same result —registered 1 unattached daemon session(s)andports.getAllreturning the planted port.
Full-app verification over CDP (RENDERER_REMOTE_DEBUG_PORT=9223 bun dev, this branch):
- Planted an unattached daemon session running
python3 -m http.server 18933(row bound to a workspace that was never selected), no renderer attach. - The dev app's host-service reaper logged
port-scan sync: registered 1 unattached daemon session(s) for scanning. - The sidebar then rendered the
18933port badge inside that workspace's item while a different workspace was selected — confirmed via CDP DOM inspection. - Dev-mode caveat: the dev daemon is not detached (dies with host-service by design), so the restart leg was covered by the scratch harness above; the dev app covered reaper registration + renderer display with no attach.
Known Limitations
- The desktop entry (unlike serve.ts) doesn't kick
startDaemonBootstrapat startup — the daemon boots on demand. The reaper's earliest warm-up (2s) can beat it and logport-scan warm-up sync failed: pty-daemon is not availableonce; later warm-ups (5–90s) and the 5-min reap succeed. Pre-existing ordering, observed once in dev; harmless but noisy.
Testing
bun run typecheck --filter=@superset/host-service --filter=@superset/desktopbun run lint(exit 0)bun test src/terminal/reaperinpackages/host-service(9 pass)bun turbo run build --filter=@superset/desktop
Risks / Rollout
- Risk: the desktop app now also runs the orphan reap it was always meant to run. Rowless alive daemon sessions are killed only after two consecutive 5-minute passes, and
status: "exited"/"disposed"rows only describe already-dead PTYs, so healthy sessions are unaffected — but this is behavior the desktop app has never exercised in production. - Rollback: revert this commit; behavior returns to today's (no reaper, ports on attach only).
Summary by cubic
Start the terminal reaper from the desktop host-service entry with startTerminalReaper(db). This cleans up orphaned sessions and reconciles ports on boot, fixing ports not showing until a workspace is opened.
- Bug Fixes
- Start the reaper in the desktop entry and export it from
@superset/host-service; both serving entries run it on boot. - Remove
startHostRuntimeand keep reaper startup outsidecreateApp.
- Start the reaper in the desktop entry and export it from
Summary by CodeRabbit
- Bug Fixes
- Improved cleanup of desktop terminal sessions during app startup, reducing stuck or orphaned terminals.
- Enhanced startup reliability for the desktop host service by adding an additional terminal reaping step once the service is ready.