Skip to content

fix(client): start the service at once when its registration names a dead process - #55882

Open
Hona wants to merge 1 commit into
anomalyco:devfrom
Hona:dead-registration
Open

Hona wants to merge 1 commit into
anomalyco:devfrom
Hona:dead-registration

Conversation

@Hona

@Hona Hona commented Oct 15, 2026

Copy link
Copy Markdown
Member

A restart or crash leaves the background service's registration file (service.json) behind. Service.ensure() gives any registration that doesn't answer a 5 s spawnDelay before it starts a replacement. So the first desktop launch after every reboot waits for a process that no longer exists.

The delay now applies only while the registered process is still running:

// packages/client/src/service-contender.ts
shouldRecruit(owner: number | undefined) {
  if (lastSpawn === 0 && owner !== undefined && running(owner)) lastSpawn = Date.now()
  ...
}

// Only ESRCH means the process is gone; EPERM is a live process this user cannot signal.
function running(pid: number) { ... process.kill(pid, 0) ... }
  • Both clients: the Promise client (used by the desktop) and the Effect client (used by the CLI) both pass info?.pid to the shared contender pool.
  • Permission errors: a process the client can't signal (EPERM, for example a service started as Administrator) still counts as running and keeps the delay.
  • Reused PIDs: if Windows has given the old PID to another program, the client waits 5 s, the same as before.

Impact: first desktop launch after a reboot

Desktop 2.0.29 with this change, registration left by a killed service (bench:startup --service stale), 4 alternating launches each.

Server spawn starts App shell visible
Before 5.66 s 6.21 s
After 0.73 s 1.47 s

On one Windows machine, 8 of the last 16 desktop launches were in this state, each waiting 5.2–7.3 s before it spawned the server.

…dead process

A restart or crash leaves the service registration behind. ensure() gave every unanswered registration a 5 s spawn delay before starting a replacement, so the first launch after each reboot waited for a process that no longer exists. The delay now applies only while the registered process is running; only ESRCH counts as gone, so a live process the client cannot signal still gets the delay.
Copilot AI balanced review requested due to automatic review settings October 15, 2026 23:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I reproduced the stale-registration wait on the base: with a service.json naming an exited PID, ensure() waited about 5 s before spawning a replacement. With this change it spawned in about 3 ms. The contender pool, service, promise-service and effect tests pass, and so does the client type check. I didn't run the desktop app or test on Windows, so the EPERM and reused-PID cases are checked by reading the code only.

Public API: unchanged. shouldRecruit is internal to service-contender.ts and not exported from the package.

No concerns.

@HootRock

Copy link
Copy Markdown

AI-assisted supplemental verification of c2f0ca1b against 13263f30 on native Windows / Bun 1.4.2.

Six public-client cases pass on head; the same cases with base sources give four passes and two failures. For both Promise and Effect Service.ensure, an actually exited child is confirmed as ESRCH and replaced by a healthy real fixture service. The live-but-unanswered PID keeps its spawn gate, and a healthy registered service is reused without a replacement. These assertions use the existing timing seam and observable spawn/reuse outcomes, not a desktop speed benchmark.

The contender-pool, Effect API and supplemental files pass 21 tests after restoring head. A separate run of the three owning lifecycle files gives the same 44 passes / 4 skips / 3 failures on both revisions: prepared handoff, overlapping-failure timing, and the stalled-survivor deadline. This is not an all-green lifecycle result. Client typecheck is blocked here by the native compiler's failed to evaluate symlinks: Access is denied panic before diagnostics. Scoped formatting passes.

Dependencies were borrowed from an existing local runtime; relevant Client/Schema/Protocol archive contents and the changed blobs were checked. No production code was changed. I found no additional regression in this PID-gate scope. Desktop startup, privileged EPERM, actual PID reuse, the full repository suite, upstream CI and live providers were not validated. This supplements the existing review; it is not formal approval.

reviewed and tested locally.

This branch has not been deployed

No deployments
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.

3 participants