partyserver: with hibernation off, a client close without a status code (1005) is never answered, so the client stays in CLOSING

Open Beginner friendly
#429 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
69/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
networking

Research direction

Start by reading the closeQuietly implementation and the change in #394; the issue identifies the reserved close codes and gives a reproduction using hibernate: false. Reproduce the code-less close and check the socket state after 6 seconds, then verify that the client receives a Close frame and leaves CLOSING after the fix.

Written by the indexing model from the issue text.

Description

Since #394, closeQuietly skips the reply for the reserved codes 1005, 1006 and 1015. 1006 and 1015 mean that no Close frame arrived. 1005 does not: it is what the server reports when the client sent a real Close frame with an empty body, which is what ws.close() with no arguments does. The transport is still open, and the client waits in CLOSING for the server's Close frame.

On a non-hibernating server (static options = { hibernate: false }) that frame never comes. y-partyserver's provider calls ws.close() with no code in disconnect(), so every YProvider.disconnect() ends this way. The provider keeps wsconnected === true until the socket finally dies, and a connect() in the meantime does nothing.

The #394 notes say compatibility dates on or after 2026-04-07 auto-reply to Close frames. That does not happen for these sockets: the result below is on compatibility date 2026-11-01.

Repro (wrangler 4.159.0, workerd 1.20261109.1, partyserver 0.5.10, y-partyserver 2.2.0, compatibility date 2026-11-01, hibernate: false):

  1. Start a YServer subclass with hibernate: false (for example via unstable_startWorker).
  2. Open a raw WebSocket to /parties/<party>/<room>, wait for open, then call ws.close() with no code.
  3. With the stock onClose, the client is still in readyState 2 (CLOSING) after 6000 ms.
  4. Add if (code === 1005) connection.close(1000, 'Closed') to onClose. The close completes in 14 ms and the client sees 1000.

In our production Worker that reply has run on every pause, reload and tab close since 2026-11-09. wrangler tail during a test run (57 events) showed no exceptions and no Network connection lost rejections.

Suggested fix: in closeQuietly, skip the reply only for 1006 and 1015. For 1005, reply with ws.close() (no code) or with 1000. A peer that sent a code-less Close frame is still there to receive the answer.

Dominant language
TypeScript
Stars
1.3k
Forks
86
PR merge metrics
No merged PRs in 30d

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from cloudflare/partykit

All issues in cloudflare/partykit

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.