feat: expose background service lifecycle#36895
Merged
Merged
Conversation
kitlangton
marked this pull request as ready for review
July 14, 2026 17:46
kitlangton
force-pushed
the
service-lifecycle-v2
branch
from
July 14, 2026 19:38
d817ebd to
453f458
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Give the V2 shared background service an observable, process-held lifecycle instead of treating registration as proof that the full application is ready.
flockor Windows named pipe.starting,ready,stopping, and latchedfailedhealth states, plus client-only discovery states.Before / After
Before: registration could point at a stale, booting, or failed process while clients treated it as a ready endpoint. Concurrent launches used a renewable file lease, replacement could race with a different instance, and a reconnecting TUI could exhaust retries or participate in replacement. A deterministic startup failure could become a client-driven respawn loop.
After: ownership is held by the process for its full lifetime. The winner binds an authenticated shell, publishes the same instance identity used by health, and only transitions to
readyafter the application layer is built. Startup failure remains bound as one actionablefailedstate. Reconnects wait for the current owner; only fresh version-enforcing launches request exact-instance replacement.How
packages/core/src/util/process-lock.tsowns cross-platform process lifetime locking, including process-death and suspension coverage.packages/protocol/src/groups/health.tsdefines the lifecycle health and exact-instance stop contracts; generated Effect and Promise clients are updated from that API.packages/server/src/process.tsbinds one stable HTTP dispatcher before boot, publishes throughonListen, activates the application atomically, fences shutdown, and latches managed startup failure.packages/server/src/service-status.tsowns the small atomic status transition model and idempotent addressed stop handling.packages/client/src/effect/service.tsdiscovers status, waits for live owners, throttles contenders, reports failures, and performs graceful replacement with legacy signal fallback.packages/cli/src/server-process.tsholds ownership for the complete process lifetime and repairs registration without displacing a live owner.packages/tui/src/context/client.tsxre-resolves cancellably while disconnected and exposes semantic service status topackages/tui/src/component/reconnecting.tsx.packages/cli/script/service-smoke.tsverifies the compiled artifact, including authentication, two-process election, readiness, exact-instance stop, and cleanup.Scope
statuscontinue to decode asready.Testing
packages/core:bun run test- 1,279 passed, 6 skipped.packages/server:bun run test- 9 passed.packages/cli:bun run test- 30 passed.packages/tui:bun run test- 226 passed, 1 skipped.packages/client:bun run test- 38 passed.bun typecheck.bun run check:generated.ready, unauthenticated health/stop returned401, the loser exited, exact-instance stop was accepted, and registration was removed.Flow
sequenceDiagram participant Client participant Registration participant Contender participant Shell as Owning lifecycle shell participant App as Application layer Client->>Registration: Read channel registration alt no responding compatible owner Client->>Contender: Spawn lightweight contender Contender->>Contender: Acquire process-held lock Note over Contender: Losing contenders exit Contender->>Shell: Bind authenticated HTTP shell Shell->>Registration: Publish URL, PID, version, instance ID, password Shell->>App: Build in a child scope App-->>Shell: Activate full router Shell-->>Client: status = ready else owner starting, stopping, or failed Shell-->>Client: Return semantic status Client->>Client: Wait or present recovery guidance end Client->>Shell: POST exact instance stop Shell-->>Client: accepted = true Shell->>Registration: Remove registration on exit