fix(ws): surface offline-session sends instead of silently dropping#58
Merged
Conversation
The hub forwarded user messages to channel.ws.send() whenever an entry
existed in the channel registry, with no check that the underlying socket
was actually OPEN. A half-open WS (TCP dead but close not yet observed)
would return a positive byte count, the hub logged "forwarding to channel",
and the message vanished — no agent ever processed it, the UI showed no
error, and the user assumed the message was sent.
Now:
- Hub treats `readyState !== OPEN` the same as `sent === -1`: unregister
the stale entry, broadcast `session_status: offline`, and emit a
`send_refused { error: 'session_offline' }` to the sender.
- `useWebSocket` clears the in-flight queue entry on `send_refused` so the
reconnect path doesn't replay a message we already told the user failed.
- `useChat` + `useChatSurface` render the refusal inline as a transient
assistant bubble with `status: 'interrupted'`, so the user sees WHY
there's no response and can reconnect their agent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
The hub forwarded
user_messagetochannel.ws.send()whenever an entry existed in the channel registry — with no liveness check on the underlying socket. A half-open WS (TCP dead, close not yet observed) returns a positive byte count fromsend(), the hub logsforwarding to channel, and the message vanishes. No agent processes it, the UI shows nothing, and the user assumes their send went through.Symptom seen in prod logs:
…repeated forever with no
text_delta/thinking/tool_use/assistant_messageever following.Fix
hub/src/ws/client.ts): before forwarding, checkchannel.ws.readyState === 1(OPEN). If not — or ifsend()returns-1— unregister the stale entry, broadcastsession_status: offline, and emit a structuredsend_refused { error: 'session_offline', reason }back to the SENDER.useWebSocket.ts): clear the in-flight queue entry onsend_refusedso the reconnect replay doesn't resurrect a message we already told the user failed.useChat.ts,useChatSurface.ts): render the refusal inline as a transient assistant bubble (status: 'interrupted') so the user sees WHY there's no response and can reconnect their agent.Test plan
bun run build:webcleanbun test hub/test/— same baseline failures unrelated to ws/client.ts)claude-remoterunning, send a message → expect inline "⚠ No live runner is attached…" bubble + session badge flips to offlineclaude-remoterunning, normal chat flow unaffected🤖 Generated with Claude Code