Service Lifecycle Deep Dive

The V2 managed service keeps a background server process that owns sessions, configuration, plugins, permissions, and tool execution. This design replaced the earlier heartbeat-lease approach after incident #36688.

Problem

The original V2 service used a heartbeat lease with a 3-second staleness timeout. This caused four failures:

  1. Multiple TUIs spawned heavyweight server contenders simultaneously
  2. A slow winner was unobservable during cold boot, so another wave displaced it
  3. A fresh TUI exhausted its reconnect budget and crashed
  4. A losing contender stayed alive consuming ~1GB RSS

Solution: Process-Held OS Lock

The new design uses flock on POSIX and an exclusively bound named pipe on Windows. The lock:

  • Is acquired before any expensive boot work
  • Is held for the entire process lifetime
  • Is released by the OS on process death (no cleanup callback needed)
  • Cannot be broken by a stale heartbeat

Lifecycle Shell

The elected process binds a minimal HTTP surface before initializing the application. This lets clients distinguish a slow winner from an absent server.

The health response includes a status discriminant:

type ServiceHealth = {
  healthy: true
  version: string
  pid: number
  instanceID: string
  status: ServiceStatus
}

During starting or stopping, application requests receive:

HTTP/1.1 503 Service Unavailable
Retry-After: 1
Content-Type: application/json

{"code":"service_starting"}

Update Activation

Background updates don’t restart the running service. A fresh TUI launch activates updates:

  1. Background updater installs the new package
  2. Running process continues with old image
  3. Fresh TUI detects version mismatch
  4. Fresh TUI requests graceful stop of the old instance
  5. Old service suspends sessions, reports stopping, exits
  6. Lock releases, a new contender wins, boots the new version
  7. TUIs reconnect and resume

Client Reconnect

Fresh and existing TUIs use the same status loop:

StatusUser-facing state
No registrationStarting background service...
Registration unreachableWaiting for background service...
startingStarting OpenCode vX...
stoppingUpdating to vX...
failedActionable failure message
readyNormal TUI

Gotcha: Transport loss is never a terminal error. TUIs rediscover and reconnect indefinitely. Only explicit service restart can perform destructive recovery of an unresponsive owner.

Launch vs Reconnect

Connection typeVersion policy
launch (fresh TUI)Requires installed package version, may activate replacement
reconnect (existing TUI)Accepts current owner, never activates replacement

This preserves permissive reconnect behavior - existing TUIs don’t disrupt the service when they reconnect after a network blip.