#4742·undici

WebSocket transitions back from CLOSED to CLOSING

Author: domenicCreated Jan 17, 2026Updated Aug 31, 2026
Labelsbugwebsocket

Bug Description

When calling close() on a CONNECTING WebSocket, the readyState property is 3 (CLOSED) during the close event handler, but incorrectly reverts to 2 (CLOSING) after the handler returns. Once a WebSocket reaches the CLOSED state, it should never transition back to CLOSING.

Reproducible By

javascript
const { WebSocket } = require('undici');

const ws = new WebSocket('wss://echo.websocket.events/');

let capturedReadyState;

ws.addEventListener('close', () => {
  capturedReadyState = ws.readyState;
  console.log('during close event, readyState =', ws.readyState);
});

ws.close();

console.log('after close(), readyState =', ws.readyState);

setTimeout(() => {
  console.log('captured readyState from inside handler =', capturedReadyState);
  console.log('after 10ms, readyState =', ws.readyState);
}, 10);

setTimeout(() => {
  console.log('after 100ms, readyState =', ws.readyState);

  // Expected: readyState should be 3 (CLOSED) after close event
  // Bug: readyState is 2 (CLOSING) after the event handler returns
  if (ws.readyState === 3) {
    console.log('PASS: readyState is CLOSED (3)');
  } else {
    console.log('FAIL: readyState is', ws.readyState, '(expected 3)');
  }
  process.exit(0);
}, 100);

setTimeout(() => {
  console.log('Timeout - something went wrong');
  process.exit(1);
}, 5000);

Expected Behavior

after close(), readyState = 2
captured readyState from inside handler = undefined
after 10ms, readyState = 2
during close event, readyState = 3
after 100ms, readyState = 3
PASS: readyState is CLOSED (3)

See: https://jsbin.com/vugosuzeqa/edit?html,js,output

This follows from how the spec synchronously changes the state to CLOSING in https://websockets.spec.whatwg.org/#dom-websocket-close , and then after closing, queues a task to change the state to CLOSED and fire events.

So, this is probably the same root cause as #4741.

Relevant WPTs:

Logs & Screenshots

Actual behavior:

during close event, readyState = 3
after close(), readyState = 2
captured readyState from inside handler = 3
after 10ms, readyState = 2
after 100ms, readyState = 2
FAIL: readyState is 2 (expected 3)

The misordering is again related to #4741.

Environment

  • Node v25.2.1
  • undici 7.18.2