Skip to content

fix(tui): desynchronize managed reconnect ensure herd#37586

Open
armancharan wants to merge 34 commits into
anomalyco:v2from
armancharan:reconnect-herd
Open

fix(tui): desynchronize managed reconnect ensure herd#37586
armancharan wants to merge 34 commits into
anomalyco:v2from
armancharan:reconnect-herd

Conversation

@armancharan

@armancharan armancharan commented Jul 18, 2026

Copy link
Copy Markdown

Issue for this PR

Related to #36285

#36285

Parent epic: #36441

Builds on ○ #37585 (stream ownership / post-ensure jitter) and ○ #37570 (ensure-on-first-failure). This PR is the reconnect-herd slice only — update/replacement UX and cold-location cost remain out of scope.

✧ raised · ○ in review · ● approved · ✓ merged

#36441 epic: scope streams + bound payloads

Merge in order:
v2
└ ○ #37486 location interest
   └ ○ #37487 session interest + fan-out bench
      └ ○ #37585 stream ownership / diagnostics
         └ ○ #37586 reconnect herd ← this PR

Siblings under the epic (not git parents of the stack tip):
└ ○ #37559 session blobs + payloadHash
└ ○ #37570 ensure-on-first-failure → feeds #37586

Stacked on #37585 (event-stream-ownership). GitHub base is v2 because the parent branch is fork-only; herd-only diff: armancharan/opencode@event-stream-ownership...reconnect-herd — retarget base onto that branch after #37585 merges.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

N TUIs reconnecting after a managed-service restart were calling Service.ensure in lockstep (attempt 1 skipped backoff), launching many serve --service contenders together.

  • Pre-ensure spread: 0..500ms jitter before every service.reconnect() / ensure so callers desynchronize
  • Keep ensure-on-first-failure: after the spread, attempt 1 still ensures immediately and skips the post-ensure 1000+jitter backoff when ensure succeeds
  • Export ServerConnection.managedReconnect + regression tests (aligned with test(cli): lock managed reconnect ensure-on-first-failure #37570) plus a seven-caller concurrent reuse test that must not spawn

How did you verify your code works?

  • packages/cli test/server-connection.test.ts — ensure-on-first-failure; version reuse; concurrent reconnects reuse one endpoint without spawning
  • packages/tui test/cli/tui/reconnect-herd.test.ts — ensure spread / backoff bounds
  • packages/tui test/cli/tui/use-event.test.tsx — attempt-1 ensure still happens after spread; cleanup aborts in-flight ensure
  • typecheck in packages/cli and packages/tui

Acceptance → test

Claim Test
Ensure spread desynchronizes reconnecting clients ensure spread stays within jitter bounds and desynchronizes samples
Post-ensure backoff stays in [1000, 1500) post-ensure backoff stays in [1000, 1500)
First reconnect still ensures (after spread) backs off when a resolved event stream keeps failing + managed reconnect ensures the service on the first failure
N concurrent reconnects reuse healthy endpoint without spawn concurrent managed reconnects reuse one healthy endpoint without spawning
Healthy mismatched version reused under reconnect managed reconnect reuses a healthy service from another version

Screenshots / recordings

N/A — not a UI layout change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Made with Cursor

armancharan and others added 15 commits July 17, 2026 21:58
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Narrow unknown event data with object/`in` checks so sessionID is read without an assertion.

Co-authored-by: Cursor <cursoragent@cursor.com>
Helpers only need directory; no test supplies a workspace.

Co-authored-by: Cursor <cursoragent@cursor.com>
No test supplies a workspace; avoid an unnecessary brand cast.

Co-authored-by: Cursor <cursoragent@cursor.com>
Filter interested subscribers once before encode, require directory for
location scope (reject workspace-only queries), and cover those cases
in tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Collect matching subscribers first so encode/offer share one pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract pure interest helpers and cover subscribeInput/equality plus
ClientProvider.event.scope reconnecting only when interest changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Serialize TUI subscribe generations, cancel SSE readers on abort, bridge
Bun disconnects so EventFeed queues release, and expose content-free
feed counters for leak detection.

Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid a hard EventFeed.Service requirement on the debug handler so CLI
serve Effect inference stays compatible with the runtime handler map.

Co-authored-by: Cursor <cursoragent@cursor.com>
Spread ensure calls with pre-ensure jitter, keep attempt-1 ensure-on-first-
failure after the spread, and lock concurrent reconnect reuse without spawn.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

Replace the hand-rolled unknown guards with decodeUnknownOption so
ephemeral sessionID extraction matches host Schema patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>
@armancharan
armancharan marked this pull request as draft July 18, 2026 02:09
armancharan and others added 8 commits July 18, 2026 12:10
Map HttpApi-decoded ctx.query through interestFromSubscribeQuery, and route
deepObject URLSearchParams through the same Schema before building Interest.

Co-authored-by: Cursor <cursoragent@cursor.com>
Also derive EventFeed.Diagnostics from the protocol Schema and lock the
generation boundary so prior-generation event IDs are not delivered.

Co-authored-by: Cursor <cursoragent@cursor.com>
One test was asserting connect, interest change, and no-op scope together.
Split into three named cases with a shared mount helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@armancharan
armancharan marked this pull request as ready for review July 18, 2026 02:44
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