fix(ui): guard first-message session hydration#494
Merged
Conversation
…ed input)
When a worker is spawned and MCP startup is slow, three races combine
to make new sessions appear "stuck in limbo":
1. initial-prompt.ts only waited for the relay to register, not for the
worker startup gate. Since relay registration takes ms while MCP can
take 10-30s, the initial prompt started streaming before MCP had
finished loading — the first turn ran without MCP tools and raced
ahead of any input buffered behind the gate.
2. connection.ts's sock.on("input") awaits the startup gate before
dispatching, which is correct. But the UI sends input with no
deliverAs when it thinks the agent is idle — and by the time the
gate releases, the initial prompt is already streaming. Calling
sendUserMessage into a streaming agent with no deliverAs throws
"Agent is already processing..." and the message is silently
dropped, leaving the UI in "limbo forever" until the user cancels
and retypes.
3. The capabilities snapshot (models, commands) is broadcast once on
relay `registered`. If MCP is still loading at that moment, the
Redis snapshot stays stale — users have to navigate away and back
to see fresh models/commands.
Fix:
- initial-prompt.ts now awaits Promise.all([waitForRelayRegistration,
waitForWorkerStartupComplete]) before dispatching the initial prompt.
- connection.ts extracts resolveInputDeliverAs() — when a requested
deliverAs is absent and rctx.isAgentActive, default to "followUp"
so the buffered input is safely queued instead of silently thrown.
- lifecycle-handlers.ts subscribes to mcp:registry_updated and
re-broadcasts capabilities so stale snapshots self-heal once MCP
finishes loading.
Regression coverage:
- initial-prompt.test.ts: delays sendUserMessage until the gate releases
- deliver-as-default.test.ts: resolveInputDeliverAs truth table
- mcp-registry-rebroadcast.test.ts: capabilities re-emit on registry update
Builds on the earlier startup-gate fix (#446 / wNXa7QPF) which wired the
gate into triggers and web-UI input but missed the initial-prompt path
and left no rescue for the deliverAs race.
Closes: oJgYHKTr
…eload socket.io-client CI ran the unit tests in a cleaner environment than my local machine and exposed a test-ordering bug I'd introduced: (fail) remote connection startup gate > buffers trigger-delivered turns… (fail) remote connection startup gate > buffers remote input… (fail) remote connection startup gate > delivers immediately when gate is not armed… All three failed with `lastSocket === null`. Bun preloads every *.test.ts file in the directory before running any test. When deliver-as-default.test.ts imported `resolveInputDeliverAs` from ./connection.js, it transitively pulled in socket.io-client before connection.startup-gate.test.ts had a chance to `mock.module` it. Subsequent calls to `io()` used the real library, the FakeSocket was never created, and `lastSocket` stayed null. Same ordering hazard affected the new mcp-registry-rebroadcast.test.ts: it imported lifecycle-handlers.ts, which transitively loads connection.ts and its socket.io-client import. Fixes: - Move `resolveInputDeliverAs` to its own file, `packages/cli/src/extensions/remote/deliver-as-default.ts`. The test imports from there, so no part of connection.ts's graph is eagerly loaded. connection.ts just imports the helper from the new file. - Delete mcp-registry-rebroadcast.test.ts. The registration it covers is three lines in lifecycle-handlers.ts and exercising it in isolation requires mocking the entire lifecycle surface, which either duplicates runtime or reintroduces the same preload problem. The behavior is a trivial pass-through; the other regression tests (deliver-as-default + initial-prompt) remain in place. Verification: - bun run typecheck — clean - cd packages/cli && bun test src — 1854 pass / 0 fail / 1854 tests (Previous commit in this branch: 1828 pass / 27 fail locally, which matched the CI red. With this fix the whole cli suite now runs green locally, which should also match CI.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test Plan