Skip to content

fix(cli): recover from concurrent service replacement during startup #35801

Description

@kitlangton

Summary

A TUI starting during managed-daemon replacement should survive the handoff. If its initial transport disappears, startup should rediscover the winning registration and retry instead of exiting with a raw transport error.

Current state

The original incident produced seven live service candidates because concurrent starts raced through registration. V2 now elects one service process with a channel-scoped file lock (dd842f9; related PR #35826), so the many-candidate race described in the original report is no longer the current architecture.

The remaining client-side requirement is still valid: a startup transport can disappear during intentional daemon replacement. Event-stream reconnect already rediscovers the managed service, but initial TUI hydration/API requests can fail before that recovery path owns startup.

Expected behavior

  • Fresh startup may replace a healthy version-mismatched daemon according to service policy.
  • Concurrent service candidates elect one owner.
  • A TUI whose startup transport disappears rediscovers the current registration and retries bounded initial hydration.
  • The user does not need to launch OpenCode a second time.
  • Explicit --server failures remain loud; only managed-service startup uses rediscovery.

Acceptance criteria

  • Reproduce a daemon handoff between transport discovery and initial TUI hydration.
  • The first TUI invocation rediscoveries the winning managed daemon and completes startup.
  • Retry is bounded and does not create restart authority beyond ordinary Service.start policy.
  • Multiple concurrent clients converge on the elected daemon.
  • Tests cover transport loss before the event stream connects and during initial hydration.

Original observation

Observed on macOS while upgrading 0.0.0-next-15078 to 0.0.0-next-15081: the first TUI exited with a transport error and a second invocation succeeded after registration stabilized.

Discussion: https://slack.com/archives/C0BE69AHCQP/p1783527674019519

Metadata

Metadata

Assignees

Labels

2.0bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions