Skip to content

Wait for plugin startup before daemon reconnect recovery - #6689

Merged
ymichael merged 1 commit into
mainfrom
bb/wait-for-plugin-startup-before-daemon-reconnect-thr_gzagydpbdz
Oct 30, 2026
Merged

ymichael merged 1 commit into
mainfrom
bb/wait-for-plugin-startup-before-daemon-reconnect-thr_gzagydpbdz

Conversation

@ymichael

@ymichael ymichael commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Human comments

What was wrong

At boot the server starts its HTTP listener and loads plugins in the background (startServerPlugins, which ends in markRegistrationsSettled). A daemon that restarted along with bb reconnects during that window. handleHostSessionOpened then immediately interrupts the turns the new daemon process no longer has, firing turn.failed and thread.failed. Plugin events go only to plugins that have already loaded and are never replayed, so a plugin still loading at boot never hears that the restart cut those turns. Turn sends already wait for plugin startup (whenRegistrationsSettled). Reconnect recovery didn't. This blocks plugins (and #6612) from reacting to turns a restart cut.

What changed

apps/server/src/internal/session-owner-side-effects.ts, in handleHostSessionOpened:

  • The replaced session is still retired right away (cancel its pending disconnect, close its socket, tell terminal sessions).
  • Recovery then waits for deps.providerRegistry.whenRegistrationsSettled(), the same wait with the same 30s cap that sends use. That covers interrupting environment provisioning, pending interactions and active turns, settling dangling background tasks, and reconcileDaemonReportedThreads. The /internal/session/open response is held until recovery finishes, so the daemon's WebSocket can't send events that conflict with the active-thread snapshot it reported. Plugin startup doesn't depend on a connected daemon, so this can't deadlock. After a boot, a machine shows as connected only once plugins have loaded (or after 30s).
  • A session replaced during the wait skips recovery. Its thread report is out of date, and the newer session's handler recovers the host using the replaced session as its previous one. Without this, an out-of-date report could revive a thread (run.started) and its turn would then be failed a second time.

No wire change, no protocol bump, no plugin SDK change.

How you verified

  • New apps/server/test/internal/reconnect-recovery-waits-for-plugins.test.ts, using a provider registry with deferRegistrationsSettled: true:
    • A daemon restarting before plugins settle: the cut turn stays active and no turn.failed fires. After settling, the thread is in error with exactly one turn.failed. Fails without the wait: the thread was already error.
    • Two daemon restarts during the wait, where the first (out-of-date) open reports the thread active: exactly one turn.failed. Fails without the replaced-session check: turn.failed fired twice.
  • pnpm exec turbo run test lint --filter=@bb/server: 368 files / 3789 tests pass, lint clean.

🤖 Generated with Claude Code

AGENT GENERATED

At boot the server accepts daemon session opens while plugins are still
loading. A daemon that restarted alongside bb had its cut turns interrupted
right away, so plugins that had not loaded yet never heard the turn.failed
and thread.failed events, which are not replayed.

handleHostSessionOpened now retires the previous session immediately but
waits for whenRegistrationsSettled, the same wait turn sends use, before
interrupting provisioning, pending interactions and active turns and
reconciling the daemon's reported threads. The session-open response is
held until then, so daemon events cannot race the reported thread snapshot.

A session superseded during that wait skips recovery: its report is stale
and the newer session recovers the host, which avoids reviving a thread
from the stale report and failing its turn twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymichael
ymichael merged commit 45ddc81 into main Oct 30, 2026
31 checks passed
@ymichael
ymichael deleted the bb/wait-for-plugin-startup-before-daemon-reconnect-thr_gzagydpbdz branch October 30, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant