MemberJunction / MemberJunction/MJ
Conversation agent completions are push-only - unstable internet can cause status to hang
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 323
Description
**Summary**
On an unstable network connection (observed on conference wifi), sending a message to an agent from the conversations UI sometimes hangs: status updates stream in normally, then stop, while the elapsed timer keeps counting — a run that normally takes ~20s shows 2, 3, 5+ minutes with no further updates and no error. Refreshing the page shows the completed response, with the run's actual (short) duration recorded.
The agent ran fine and the result was persisted correctly throughout. This isn't a race condition — the completion event has exactly one live delivery path (a WebSocket push with no buffering or replay), and that path can die silently without the client, or the user, ever learning it died.
Full mechanism breakdown, a detection-asymmetry timeline, why each existing recovery path misses, proposed fixes, and a healthy-path cost analysis: https://claude.ai/code/artifact/3ffe7c1f-b6fb-49be-b4da-9b1d6721d49d
**Reproduce (simulated)**
1. Open a conversation, send a message to an agent whose run takes a few seconds.
2. Mid-run, black-hole outbound traffic to the WebSocket port without sending FIN/RST (a local firewall rule, or physically disconnecting a wifi adapter mid-stream) — devtools' offline toggle or killing the tab both send clean close frames and mask the bug.
3. Wait. Status updates stop; the elapsed timer keeps ticking with no error state.
4. Refresh. The completed response appears, with the original short duration.
**Root cause (short version)**
Conversation agent runs are fire-and-forget (packages/GraphQLDataProvider/src/graphQLAIClient.ts:623) — completion arrives only over the statusUpdates GraphQL subscription, backed by an in-memory graphql-subscriptions PubSub with no replay (packages/MJServer/src/index.ts:835-842). The client's createClient call sets keepAlive but implements no ping/pong timeout (packages/GraphQLDataProvider/src/graphQLDataProvider.ts:3079-3094), so a half-open socket never fires close — the client believes the channel is healthy while nothing arrives. The server detects the same dead link in ~12s and publishes the completion into an empty topic; it's unrecoverable from there. See the spec for the full timeline and why the three existing backstops (idle reconcile, agent-state polling, load-time reconcile) all miss this window.
**Suggested fixes**
1. Client-side dead-socket detection — implement graphql-ws's documented ping/pong timeout so a half-open socket produces a real close event.
2. Reconcile in-progress runs on socket reconnect (recovers the completion dropped during the outage — (1) alone only restores the channel going forward).
3. Same reconcile on tab focus / online event.
4. Retry/timeout hardening on the graphql-ws client (retryAttempts, connectionAckWaitTimeout).
5. Surface connection state in the UI instead of a timer with no ceiling.
None of these add polling to the healthy path — each triggers only on a state transition (a ping bounce, a reconnect, a tab regaining focus) that doesn't occur on a stable connection. See spec §06 for the per-fix cost breakdown.
Contributor guide
Assessment
This issue has not been assessed yet.