jackwener/OpenCLI · error · CommandExecutionError

`${label} returned malformed JSON: ${err?.message ?? err}`

Error message

`${label} returned malformed JSON: ${err?.message ?? err}`

What it means

eolFetch wraps fetch calls to endoflife.date and expects an HTML/JSON response whose body parses as JSON. When resp.json() throws — meaning the server returned non-JSON (HTML error page, empty body, truncated response) — it rethrows as CommandExecutionError with the label prefixed so you know which endpoint failed. The underlying parse error message is appended after 'returned malformed JSON:'.

Source

Thrown at clis/endoflife/utils.js:70

    }
    if (resp.status === 404) {
        throw new EmptyResultError(label, `endoflife.date returned 404 for ${url}.`);
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'endoflife.date throttles unauthenticated traffic; wait a few seconds and retry.',
        );
    }
    if (!resp.ok) {
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
    }
    let body;
    try {
        body = await resp.json();
    }
    catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    return body;
}

// endoflife.date returns scalar fields that are either an ISO date "YYYY-MM-DD",
// a boolean (true = supported / ongoing, false = not LTS), or null. Normalise:
// - boolean true -> the literal string "ongoing"
// - boolean false -> null (matches "no LTS phase" / "not in extended support")
// - date string -> as-is
// - anything else -> null
export function normaliseDateOrFlag(value) {
    if (value === true) return 'ongoing';
    if (value === false || value == null) return null;
    if (typeof value === 'string') {
        const s = value.trim();
        return s || null;
    }
    return null;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command — transient truncations or Cloudflare blips often resolve on retry.
  2. Check status with curl -s https://endoflife.date/api/all | head to see what the server actually returns.
  3. Check for a corporate proxy/firewall injecting an HTML block page and add an exception for endoflife.date.
  4. Inspect the CommandExecutionError message: the text after 'returned malformed JSON:' is the underlying fetch JSON parse error (e.g. 'Unexpected token < in JSON').

Example fix

// before
const cycles = await cycles('nodejs');

// after
let cycles;
try {
  cycles = await cycles('nodejs');
} catch (err) {
  if (err instanceof CommandExecutionError && err.message.includes('returned malformed JSON')) {
    cycles = await cycles('nodejs'); // retry once on transient non-JSON response
  } else {
    throw err;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const resp = await fetch(url);
const text = await resp.text();
if (!resp.ok || !text.trim().startsWith('{') && !text.trim().startsWith('[')) {
  throw new Error(`endoflife.date returned non-JSON (HTTP ${resp.status})`);
}

Type guard

function isJsonObject(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

Try / catch

try {
  const data = await eolFetch(url, label);
} catch (err) {
  if (err instanceof CommandExecutionError && err.message.includes('returned malformed JSON')) {
    // fall back to cached data or retry with backoff
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling cycles (or any eolFetch consumer) when endoflife.date responds with non-JSON: an HTML 5xx/Cloudflare error page, a 404 body, a rate-limit page, or an empty/truncated body, so resp.json() throws.

Common situations: endoflife.date outage or Cloudflare challenge page; network middlebox/proxy injecting an HTML block page; hitting the wrong URL after an API path change; intermittent connection drop truncating the response mid-body.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/8e24ba6d97f39bc8. Report an issue: GitHub.