Skip to content

fix(ws): surface offline-session sends instead of silently dropping#58

Merged
finedesignz merged 1 commit into
mainfrom
fix/send-message-no-response
May 26, 2026
Merged

fix(ws): surface offline-session sends instead of silently dropping#58
finedesignz merged 1 commit into
mainfrom
fix/send-message-no-response

Conversation

@finedesignz
Copy link
Copy Markdown
Owner

Summary

The hub forwarded user_message to channel.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 from send(), the hub logs forwarding 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:

[agent] authenticated session=41d0cb2c... reused=true
[client] send_message session=41d0cb2c...
[client] forwarding to channel session=41d0cb2c...

…repeated forever with no text_delta/thinking/tool_use/assistant_message ever following.

Fix

  • Hub (hub/src/ws/client.ts): before forwarding, check channel.ws.readyState === 1 (OPEN). If not — or if send() returns -1 — unregister the stale entry, broadcast session_status: offline, and emit a structured send_refused { error: 'session_offline', reason } back to the SENDER.
  • Web (useWebSocket.ts): clear the in-flight queue entry on send_refused so the reconnect replay doesn't resurrect a message we already told the user failed.
  • Web (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:web clean
  • Existing hub unit tests still pass (bun test hub/test/ — same baseline failures unrelated to ws/client.ts)
  • Manual prod verification: with no claude-remote running, send a message → expect inline "⚠ No live runner is attached…" bubble + session badge flips to offline
  • With claude-remote running, normal chat flow unaffected

🤖 Generated with Claude Code

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>
@finedesignz finedesignz merged commit 184d30f into main May 26, 2026
1 check passed
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