koala73/worldmonitor · error

global fetch is unavailable — Node 18+ is required

Error message

global fetch is unavailable — Node 18+ is required

What it means

cli/src/run.mjs resolves its HTTP client as io.fetch || globalThis.fetch. Node releases before 18 have no global fetch (undici landed in Node 18), so run() aborts with this error before attempting any request; the generic catch maps it to exit code 1. The repository standardizes on Node 24 via .nvmrc.

Source

Thrown at cli/src/run.mjs:76

  const env = io.env || (typeof process !== 'undefined' ? process.env : {});
  const stdout = io.stdout || ((s) => process.stdout.write(s));
  const stderr = io.stderr || ((s) => process.stderr.write(s));

  const parsed = parseArgs(argv);
  const { command, options } = parsed;

  if (options.version) {
    stdout(`${VERSION}\n`);
    return 0;
  }
  if (options.help || !command || command === 'help') {
    stdout(`${HELP}\n`);
    return 0;
  }

  try {
    if (!fetchImpl) {
      throw new Error('global fetch is unavailable — Node 18+ is required');
    }

    const config = resolveConfig(env);
    const plan = planRequest(parsed, config);

    if (plan.kind === 'list') {
      const specUrl = plan.specUrl || DEFAULT_SPEC_URL;
      const headers = { 'user-agent': USER_AGENT, accept: 'application/json' };
      const apiKey = options.apiKey || config.apiKey;
      if (apiKey) headers[API_KEY_HEADER] = apiKey;
      const res = await withTimeout(options.timeout, (signal) =>
        fetchImpl(specUrl, { headers, signal }),
      );
      const spec = parseBody(await res.text(), res.headers);
      if (!res.ok) {
        stderr(`${formatOutput(spec, options)}\n`);
        if (res.status === 401) stderr(`${AUTH_HINT}\n`);
        return 1;

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Check node --version and upgrade to Node 18+ — ideally Node 24 per the repo's .nvmrc (nvm use)
  2. In CI, switch the base image to node:20+ or node:24
  3. When embedding run() programmatically, inject a client: run(argv, { fetch: myFetch })
  4. If a polyfill is unavoidable in the short term, set globalThis.fetch = require('undici').fetch before importing the CLI

Example fix

# before
$ node --version
v16.20.2
$ worldmonitor tools
# Error: global fetch is unavailable — Node 18+ is required

# after
$ nvm use          # picks up .nvmrc (Node 24)
v24.x.x
$ worldmonitor tools
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast with a clear message before any CLI work
if (typeof globalThis.fetch !== 'function') {
  console.error('Node 18+ required (global fetch missing). Current:', process.version);
  process.exit(1);
}

Type guard

type FetchLike = typeof globalThis.fetch;
function hasGlobalFetch(): fetch is FetchLike {
  return typeof globalThis.fetch === 'function';
}

Prevention

When it happens

Trigger: Executing the CLI under Node 16/14/10 (system node, stale nvm alias, CI image, Docker base); embedding run() without injecting io.fetch; exotic runtimes that lack a global fetch implementation.

Common situations: Dev machine defaulting to an old LTS via nvm; CI base image (node:16) never updated; tool managers shimming an old Node; calling run() from a test harness that forgets the fetch injection.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/4b512a8a9a40f972. Report an issue: GitHub.