Skip to content

fix(cli): harden managed service election#37645

Merged
kitlangton merged 3 commits into
v2from
doom-loop-repro
Jul 18, 2026
Merged

fix(cli): harden managed service election#37645
kitlangton merged 3 commits into
v2from
doom-loop-repro

Conversation

@kitlangton

@kitlangton kitlangton commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What

Prevent stale or unresponsive managed-port owners from trapping service contenders, while preserving correct ownership during slow and highly concurrent startup. Incumbent recognition now has a true 15-second wall-clock bound, and only the process that wins the port bind persists the generated service password.

Before / After

Before: incumbent recognition allowed 60 retries, but each authenticated health probe could take two seconds, stretching an intended short retry window to roughly two minutes. A strict six-second replacement proved too short under Windows CI and misclassified a legitimate owner that published registration after seven seconds. Concurrent first-start contenders also generated and wrote passwords before election; they raced on the shared config temp file, causing ENOENT, and could persist a loser password that did not authenticate the elected service.

After: recognition is interrupted after 15 seconds regardless of probe latency. A legitimate owner may publish registration eight seconds after the bind race begins and is still recognized; a stale registration plus a listener that never answers health fails with an actionable conflict in bounded time. Passwords are generated in contender memory but persisted only in the elected owner’s onListen path, before registration, so config and registration always carry the same credential.

How

  • packages/cli/src/server-process.ts: bound the complete incumbent-recognition retry operation with Effect.timeoutOption("15 seconds").
  • packages/cli/src/server-process.ts: defer first-time password persistence until after successful port binding and before publishing registration.
  • packages/cli/test/service.test.ts: model a stale registration whose exact-port listener accepts health requests but never responds.
  • packages/cli/test/service.test.ts: deterministically wait until post-bind incumbent polling begins, delay registration for eight seconds, and verify successful adoption.
  • packages/cli/test/service.test.ts: require all election losers to exit successfully, include their output in failures, verify persisted and registered passwords match, preserve empty-password generation behavior, and prevent cleanup assertions from masking the primary failure.
  • packages/cli/test/service.test.ts: bind foreign listeners explicitly to 127.0.0.1 so the reproducer is deterministic on macOS.

Scope

This builds on the port-bind election and registration lease introduced by #37572 and #37576. It deliberately does not kill an unknown port owner: without a matching authenticated registration, signaling that PID could terminate an unrelated or PID-reused process. The safe outcome is a bounded, actionable conflict.

Testing

  • bun run test test/service.test.ts in packages/cli (17 passed)
  • bun run test test/service.test.ts --test-name-pattern "concurrent service processes elect one server" --rerun-each 20 in packages/cli (20 passed)
  • bun run test test/service.test.ts test/promise-service.test.ts in packages/client (15 passed)
  • bun typecheck in packages/cli
  • push hook full-repository typecheck (32 packages passed)

The prior Windows run also hit an unrelated packages/core/test/vcs.test.ts timeout at 5006.92 ms against a 5000 ms default; no files in that package are changed here.

@kitlangton kitlangton changed the title test(cli): bind service conflict fixture to IPv4 fix(cli): bound service incumbent recognition Jul 18, 2026
@kitlangton kitlangton changed the title fix(cli): bound service incumbent recognition fix(cli): harden managed service election Jul 18, 2026
@kitlangton
kitlangton merged commit 4e56998 into v2 Jul 18, 2026
10 checks passed
@kitlangton
kitlangton deleted the doom-loop-repro branch July 18, 2026 18:16
github-actions Bot pushed a commit to ReStranger/opencode that referenced this pull request Jul 19, 2026
* upstream/v2:
  mini: fix shell tool output display (anomalyco#37711)
  fix(core): detach disposed MCP registrations from root scope (anomalyco#37660)
  fix(core): preserve the first terminal failure (anomalyco#37705)
  fix(core): continue after malformed tool input (anomalyco#37701)
  fix(core): safely recover malformed tool input (anomalyco#37698)
  fix(simulation): render screenshot symbol glyphs (anomalyco#37691)
  fix(core): authorize relative external paths (anomalyco#37689)
  fix(tui): auto-approve permissions in auto mode
  feat(core): allow MCP Code Mode opt-out (anomalyco#37681)
  fix(codemode): stop leaking undefined into tool arguments (anomalyco#37652)
  fix(tui): style interrupted compaction neutrally (anomalyco#37655)
  fix(cli): harden managed service election (anomalyco#37645)
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