websockets/ws · error · RangeError

The message must not be greater than 123 bytes

Error message

The message must not be greater than 123 bytes

What it means

Thrown by Sender.close() when the UTF-8 byte length of the close-frame reason string exceeds 123 bytes. RFC 6455 caps all control frame (opcode 0x08) payloads at 125 bytes; the first 2 bytes carry the status code, leaving only 123 bytes for the human-readable reason. The library enforces this in sender.js:195-198 before attempting to build the frame, so an oversized reason never reaches the wire.

Solutions

  1. Truncate the reason to at most 123 bytes before calling close(): use reason = Buffer.from(reason).subarray(0, 123).toString() or slice the string so Buffer.byteLength(reason) <= 123.
  2. If you only need a status code, omit the reason entirely: ws.close(1000).
  3. Send lengthy diagnostic details via a preceding normal data message (ws.send) before closing, then close with a short or empty reason.

Example fix

// before
ws.close(1000, longErrorStackMessage);

// after
const reason = Buffer.from(longErrorStackMessage).subarray(0, 123).toString();
ws.close(1000, reason);
Defensive patterns

Strategy: validation

Validate before calling

// Run before ws.close(code, reason) or sender.close()
function safeCloseReason(reason) {
  if (reason == null) return undefined;
  const buf = Buffer.from(reason);
  return buf.length > 123 ? buf.subarray(0, 123).toString() : reason;
}
// usage: ws.close(1000, safeCloseReason(longMessage));

Type guard

function isValidCloseReason(data) {
  return (
    typeof data === 'string' ||
    (data != null && typeof data === 'object' && ArrayBuffer.isView(data) && data.BYTES_PER_ELEMENT === 1)
  ); // string or Uint8Array/Buffer
}

Try / catch

try {
  ws.close(code, reason);
} catch (err) {
  if (err instanceof RangeError && /123 bytes/.test(err.message)) {
    ws.close(code, String(reason).slice(0, 123));
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling ws.close(code, reason) or the internal sender.close(code, data, mask, cb) where Buffer.byteLength(data) > 123. The check fires only when both a numeric code and a non-empty data argument are supplied (code !== undefined and data.length > 0), because the 123-byte limit applies to the reason body that follows the 2-byte status code.

Common situations: Passing a long diagnostic/stack-trace/error description as the close reason during graceful shutdown; internationalized reason strings whose multi-byte UTF-8 encoding exceeds 123 bytes even though the character count looks short; copying exception messages verbatim into ws.close() without truncation.

Related errors


AI-assisted analysis of websockets/ws@c791e707ea (2026-08-06). Data as JSON: /api/errors/bcc03068003ec1f3. Report an issue: GitHub.

Appendix: source

Thrown at lib/sender.js:198

   * @param {Boolean} [mask=false] Specifies whether or not to mask the message
   * @param {Function} [cb] Callback
   * @public
   */
  close(code, data, mask, cb) {
    let buf;

    if (code === undefined) {
      buf = EMPTY_BUFFER;
    } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
      throw new TypeError('First argument must be a valid error code number');
    } else if (data === undefined || !data.length) {
      buf = Buffer.allocUnsafe(2);
      buf.writeUInt16BE(code, 0);
    } else {
      const length = Buffer.byteLength(data);

      if (length > 123) {
        throw new RangeError('The message must not be greater than 123 bytes');
      }

      buf = Buffer.allocUnsafe(2 + length);
      buf.writeUInt16BE(code, 0);

      if (typeof data === 'string') {
        buf.write(data, 2);
      } else if (isUint8Array(data)) {
        buf.set(data, 2);
      } else {
        throw new TypeError('Second argument must be a string or a Uint8Array');
      }
    }

    const options = {
      [kByteLength]: buf.length,
      fin: true,
      generateMask: this._generateMask,

View on GitHub (pinned to c791e707ea)