denoland/deno · error · ERR_INVALID_ARG_TYPE

ERR_INVALID_ARG_TYPE

ERR_INVALID_ARG_TYPE

Error message

ERR_INVALID_ARG_TYPE("historySize", "number", historySize)

What it means

options.historySize of readline.createInterface (the max number of retained history lines, default 30) must be a number when provided. A string, boolean, null, or object throws ERR_INVALID_ARG_TYPE('historySize', 'number'). undefined falls back to the default, and NaN numbers are handled by the separate range check that follows.

Source

Thrown at ext/node/polyfills/internal/readline/interface.mjs:239

    input = input.input;
  }

  if (completer !== undefined && typeof completer !== "function") {
    throw new ERR_INVALID_ARG_VALUE("completer", completer);
  }

  if (history === undefined) {
    history = [];
  } else {
    validateArray(history, "history");
  }

  if (historySize === undefined) {
    historySize = kHistorySize;
  }

  if (typeof historySize !== "number") {
    throw new ERR_INVALID_ARG_TYPE("historySize", "number", historySize);
  }

  if (NumberIsNaN(historySize) || historySize < 0) {
    throw new ERR_OUT_OF_RANGE("historySize", ">= 0", historySize);
  }

  // Backwards compat; check the isTTY prop of the output stream
  //  when `terminal` was not specified
  if (terminal === undefined && !(output === null || output === undefined)) {
    terminal = !!output.isTTY;
  }

  const self = this;

  this.line = "";
  this[kSubstringSearch] = null;
  this.output = output;
  this.input = input;

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Convert numeric strings at the boundary: Number(options.historySize)
  2. Validate config once: typeof cfg.historySize === 'number' or coerce and default
  3. Omit the option to accept the default 30

Example fix

// before
createInterface({ input, output, historySize: process.env.HIST_SIZE }); // string

// after
createInterface({ input, output, historySize: Number(process.env.HIST_SIZE) || 30 });
Defensive patterns

Strategy: type-guard

Validate before calling

function readHistorySize(cfg: Record<string, unknown>): number | undefined {
  const n = Number(cfg.historySize);
  return Number.isFinite(n) && n >= 0 ? n : undefined;
}

readline.createInterface({ input, output, historySize: readHistorySize(cfg) });

Type guard

const isHistorySize = (v: unknown): v is number =>
  typeof v === 'number' && !Number.isNaN(v) && v >= 0;

Try / catch

try {
  rl = readline.createInterface({ input, historySize: size as number });
} catch (err) {
  if (err?.code === 'ERR_INVALID_ARG_TYPE' && /historySize/.test(err.message)) {
    rl = readline.createInterface({ input, historySize: Number(size) || 30 });
  } else throw err;
}

Prevention

When it happens

Trigger: createInterface({ historySize: '100' }) from parsed config/CLI args; historySize: parseInt(input) where parseInt returned NaN is caught here only if the argument was not a number type at all — strings are the common case; passing the whole options object as historySize.

Common situations: REPL tool configuration read from JSON/env where every value is a string; merging user preferences objects that carry stringified sizes.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/35b0a4298b995a7a. Report an issue: GitHub.