Skip to content

fix(client): preserve compatible background service#36583

Merged
kitlangton merged 3 commits into
v2from
service-start-lock
Jul 13, 2026
Merged

fix(client): preserve compatible background service#36583
kitlangton merged 3 commits into
v2from
service-start-lock

Conversation

@kitlangton

@kitlangton kitlangton commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What

Stop a transient health-check failure from turning a healthy, same-version background service into a destructive restart. Concurrent CLI windows now converge on the existing service instead of one window invalidating an endpoint another window has already started using.

Before / After

Trigger: A background service running version V is healthy overall, but CLI A's first version-gated health probe transiently fails or times out. Before CLI A performs its fallback probe, CLI B successfully resolves that same service and begins using its endpoint.

Before: CLI A performed a second, ungated health probe. Even when that probe succeeded and reported the requested version V, Service.start() inferred that every successful fallback result was a version mismatch. It invoked the replacement callback, killed the service, and spawned another one. This produced both observed symptoms:

  • The preflight UI displayed an update from V to the identical version V.
  • CLI B retained the endpoint it had just resolved, but CLI A terminated the process behind it, so CLI B's first API request could fail with ClientError: Transport.

After: The fallback probe retains the version reported by a modern health response. When it matches the requested version, Service.start() returns the existing endpoint without invoking onStart, killing the service, or changing its registration. Both CLI windows keep using the same live process.

Genuine version mismatches still replace the daemon. Legacy health responses retain their existing replacement behavior. Missing or unhealthy services still follow the existing spawn path. Concurrent daemon spawning remains protected by the existing server-side election lock.

How

  • packages/client/src/effect/service.ts carries the health response's version through the private probe result and explicitly checks compatibility before replacement.
  • packages/client/test/service.test.ts deterministically pauses CLI A's first probe while CLI B resolves the service, then verifies the fallback does not fire onStart, replace the atomic registration, change PID/version, or kill CLI B's endpoint.
  • The same test file verifies that a legacy unversioned health response is still replaced.
  • packages/client/test/fixture/service.ts provides atomic modern and legacy registrations plus the health-probe barrier.
  • .changeset/calm-services-start.md records the Client patch.
sequenceDiagram
    participant A as CLI A
    participant B as CLI B
    participant S as Background service V
    A->>S: version-gated health probe
    S--xA: transient failure
    B->>S: version-gated health probe
    S-->>B: healthy, version V
    B-->>B: keep endpoint S
    A->>S: ungated fallback probe
    S-->>A: healthy, version V
    A-->>A: compare V == V
    A-->>A: keep endpoint S
Loading

Scope

This PR fixes the destructive same-version classification that produced the equal-version update and invalidated a concurrently resolved endpoint. Broader pre-existing server registration-finalizer and prolonged multi-probe recovery races are not changed here.

Testing

  • cd packages/client && bun run test test/service.test.ts
  • Repeated the focused regression 10 consecutive times after each simplify pass.
  • cd packages/client && bun typecheck
  • cd packages/cli && bun run test test/service.test.ts
  • Push hook: repository-wide Turbo typecheck, 31 packages passed after every push.
  • GitHub CI passed on Linux and Windows before the final test-only simplification; the final push reruns those checks.
  • cd packages/client && bun run test: 25 passed; 2 unrelated existing failures expect server.mcp while the generated client exposes mcp.
  • bunx prettier --check packages/client/src/effect/service.ts packages/client/test/service.test.ts packages/client/test/fixture/service.ts .changeset/calm-services-start.md

@kitlangton kitlangton self-assigned this Jul 13, 2026
@kitlangton kitlangton marked this pull request as ready for review July 13, 2026 01:32
@kitlangton kitlangton merged commit 397993e into v2 Jul 13, 2026
10 checks passed
@kitlangton kitlangton deleted the service-start-lock branch July 13, 2026 01:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant