Skip to content

server: #send has no readyState guard — sub whose socket closes mid-snapshot throws uncaught #40

Description

@grrowl

Found 2026-07-30 during agent-canvas's live-workspace delivery, against 0.6.0 (uncaught exceptions in a @cloudflare/vitest-pool-workers real-DO run; reproducible whenever a client subscribes then closes quickly). Verified still present on current main (post-#36, 2026-08-26): #send at src/server/mixin.ts:1082 calls ws.send(encoded) unconditionally — no readyState check, no try/catch.

Symptom

TypeError: Can't call WebSocket send() after close().
    at #send → #handleSub → #dispatch → webSocketMessage

logged as uncaught exception; source = Uncaught (in promise). Fires when a client sends sub and the socket transitions to CLOSING/CLOSED before the server finishes streaming the snapshot — the normal shape of a client disposing, navigating away, a StrictMode teardown, or a forced reconnect drop.

Expected

A socket that has gone away receiving a snapshot it can no longer read is expected under normal client churn — the socket's own close handler already tears down its subs — and should not surface as an uncaught error. Guard the send: skip when readyState !== OPEN, and/or wrap ws.send in try/catch treating a post-close send as a benign no-op.

Impact

Low–medium. No state corruption (outbound only, not the write path), but noisy uncaught-exception spam for every client that disconnects mid-handshake, and in a test harness it pollutes output and risks being flagged as a failure.

Acceptance criteria

  • sub (or any frame) arriving on a socket that closes mid-response produces no uncaught exception; the send is a no-op.
  • No behavior change on the live path — deltas/receipts to OPEN sockets unaffected; the broadcast path (Broadcaster) covered too if it shares the same raw-send exposure.
  • Test pins the abrupt-close-mid-snapshot shape (real DO, hibernatable socket) and would fail on today's unguarded ws.send.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions