feat(desktop): move server to utilityProcess#25962
Conversation
Move server initialization to a utility process (sidecar) for better isolation and resource management. This decouples the server lifecycle from the main Electron process, improving stability and startup control.
Hona
left a comment
There was a problem hiding this comment.
Reviewed the utilityProcess lifecycle against VS Code's implementation. The direction is right; these comments are about matching VS Code's timeout, env, crash, and shutdown semantics before merge.
| child.stdout?.on("data", (chunk: Buffer) => options.onStdout?.(chunk.toString("utf8").trimEnd())) | ||
| child.stderr?.on("data", (chunk: Buffer) => options.onStderr?.(chunk.toString("utf8").trimEnd())) | ||
|
|
||
| await new Promise<void>((resolve, reject) => { |
There was a problem hiding this comment.
This startup wait needs a timeout. As written, the desktop can hang forever if the sidecar stalls before posting ready/error (module import, sqlite migration, native binding load, Server.listen). The 30s health timeout in index.ts starts only after spawnLocalServer() resolves, so it does not cover this phase. VS Code wraps utility-process connection and ready/initialized handshakes with explicit 60s timeouts (localProcessExtensionHost.ts around its connect and handshake paths). Please race ready/error/exit with a startup timeout and kill the utility process on timeout.
| }) | ||
| parentPort.postMessage({ type: "ready" }) | ||
| } catch (error) { | ||
| parentPort.postMessage({ type: "error", error: serializeError(error) }) |
There was a problem hiding this comment.
This startup error path posts the error but leaves the utility process alive. The parent rejects, but nothing guarantees this sidecar exits or gets killed. VS Code treats utility-process exit/crash as lifecycle state and cleans up process state immediately. Please process.exit(1) after posting the startup error, and have the parent kill on rejected startup as a backup.
| return { | ||
| listener: { | ||
| stop: () => { | ||
| child.postMessage({ type: "stop" }) |
There was a problem hiding this comment.
Shutdown is fire-and-forget here. Callers like relaunch/update/quit immediately continue, so this 2s force-kill timer may never fire if Electron main exits first. VS Code has waitForExit(maxWaitTimeMs) that waits for exit, then force kills, and WindowUtilityProcess wires that into window lifecycle grace time. Please make SidecarListener.stop() return a Promise<void> that waits for stopped or exit, then kills after a grace period; then await it in relaunch/update paths where possible.
| cors: ["oc://renderer"], | ||
| const child = utilityProcess.fork(join(dirname(fileURLToPath(import.meta.url)), "sidecar.js"), [], { | ||
| cwd: process.cwd(), | ||
| env: process.env, |
There was a problem hiding this comment.
This passes the live mutable process.env object directly. VS Code's utility-process wrapper clones env, applies explicit process metadata, removes dangerous env vars, handles platform-specific env adjustments, and stringifies every value before spawn (utilityProcess.ts:createEnv). We should create a fresh env object here, apply only the sidecar values we need, sanitize if possible, and ensure all values are strings rather than passing process.env by reference.
| needsMigration: options.needsMigration, | ||
| }) | ||
| }) | ||
| child.on("exit", (code: number) => options.onExit?.(code)) |
There was a problem hiding this comment.
We should also wire the utility-process crash/error path, not just a plain exit callback. VS Code registers stdout, stderr, message, spawn, exit, and app.child-process-gone filtered by service name so it can distinguish clean exit from crash/oom/launch-failed and clear process state. At minimum add child.on('error') and app.on('child-process-gone') filtering serviceName: 'opencode server' so sidecar failures are visible in logs.
Summary
Move server initialization to a utility process (sidecar) for better isolation and resource management. This decouples the server lifecycle from the main Electron process, improving stability and startup control.
Changes
sidecar.tsutility process for running the serverserver.tsto spawn a utility process instead of importing server directlyindex.tsto handle sidecar lifecycle and SQLite migration progresselectron.vite.config.tsenv.d.tsBenefits