websockets/ws · error · TypeError

First argument must be a valid error code number

Error message

First argument must be a valid error code number

What it means

Thrown (as TypeError) by `Sender.close()` when the `code` argument is provided but is not a number, or is a number that fails `isValidStatusCode()`. Per RFC 6455 §7.4, the only valid status codes are 1000–1014 (excluding the reserved 1004, 1005, and 1006) and the application range 3000–4999. Codes 1005/1006 must never be sent on the wire (they are generated internally), and 1004 is undefined/unused, so the library rejects them.

Solutions

  1. Use a defined code: 1000 (normal closure), 1001 (going away), 1002 (protocol error), 1003 (unsupported data), 1007–1011, or a custom 3000–4999.
  2. Coerce string inputs to numbers and check the range before calling `close()`.
  3. Omit `code` (and `reason`) entirely to send a close frame with no status code — `ws.close()` is always valid.

Example fix

// before
ws.close(999, 'done');

// after
ws.close(1000, 'done');
Defensive patterns

Strategy: validation

Validate before calling

// Validate a close code before calling ws.close().
function isValidStatusCode(code) {
  return (
    (code >= 1000 &&
      code <= 1014 &&
      code !== 1004 &&
      code !== 1005 &&
      code !== 1006) ||
    (code >= 3000 && code <= 4999)
  );
}
function safeClose(ws, code, reason) {
  if (code === undefined) return ws.close();
  if (typeof code !== 'number' || !isValidStatusCode(code)) return ws.close();
  return ws.close(code, reason);
}

Type guard

function isValidCloseCode(code) {
  return (
    typeof code === 'number' &&
    Number.isInteger(code) &&
    ((code >= 1000 &&
      code <= 1014 &&
      code !== 1004 &&
      code !== 1005 &&
      code !== 1006) ||
      (code >= 3000 && code <= 4999))
  );
}

Try / catch

try {
  ws.close(maybeCode, reason);
} catch (err) {
  if (err instanceof TypeError && /valid error code/.test(err.message)) {
    // Fall back to a code-less close frame.
    ws.close();
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `ws.close(999, 'reason')` (below range), `ws.close(1005)` (reserved, must not be sent), `ws.close(2000, 'reason')` (outside 3000–4999), `ws.close('1000')` (string not number), or `ws.close(undefined, 'reason')` passed through carelessly.

Common situations: Passing a string status code from a config/env var without coercion; using a 'magic' code outside the defined ranges; copy-pasting a code that is reserved (1004/1005/1006); passing a close code from the wrong end of a mapping table.

Related errors


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

Appendix: source

Thrown at lib/sender.js:190

    return [target, data];
  }

  /**
   * Sends a close message to the other peer.
   *
   * @param {Number} [code] The status code component of the body
   * @param {(String|Buffer)} [data] The message component of the body
   * @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 {

View on GitHub (pinned to c791e707ea)