jackwener/OpenCLI · error · CommandExecutionError

Chess.com API returned malformed JSON for ${url}: ${error?.m

Error message

Chess.com API returned malformed JSON for ${url}: ${error?.message || error}

What it means

A CommandExecutionError thrown when resp.json() rejects because the body is not valid JSON. The library wraps the underlying parse error (with its message) alongside the requested URL to pinpoint which endpoint returned the bad body.

Source

Thrown at clis/chess/utils.js:60

export async function chessApi(path, fetchImpl = fetch) {
    const url = path.startsWith('http') ? path : `${API_BASE}${path}`;
    let resp;
    try {
        resp = await fetchImpl(url, { headers: { 'User-Agent': UA, accept: 'application/json' } });
    } catch (error) {
        throw new CommandExecutionError(`Failed to fetch Chess.com API ${url}: ${error?.message || error}`);
    }
    if (!resp || typeof resp !== 'object') {
        throw new CommandExecutionError(`Chess.com API returned an invalid response object for ${url}`);
    }
    if (resp.status === 404) throw new EmptyResultError(`Chess.com returned 404 for ${url}`);
    if (!resp.ok) throw new CommandExecutionError(`Chess.com API returned HTTP ${resp.status} for ${url}`);
    let payload;
    try {
        payload = await resp.json();
    } catch (error) {
        throw new CommandExecutionError(`Chess.com API returned malformed JSON for ${url}: ${error?.message || error}`);
    }
    if (!isPlainObject(payload)) {
        throw new CommandExecutionError(`Chess.com API returned an unexpected payload shape for ${url}`);
    }
    return payload;
}

/** Pull rating + record fields out of a stats sub-object (`chess_rapid` etc). */
export function summarizeStats(stats, kind) {
    const k = stats?.[kind];
    if (!k) return null;
    if (!isPlainObject(k)) {
        throw new CommandExecutionError(`Chess.com stats payload for ${kind} is not an object`);
    }
    if (!isOptionalPlainObject(k.last)) {
        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.last is not an object`);
    }
    if (!isOptionalPlainObject(k.best)) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the request — transient proxy/CDN glitches often resolve.
  2. curl the exact URL from the error message and inspect whether the body is HTML or empty.
  3. Check for captive-portal/proxy interference (try a different network).
  4. If you inject a fetchImpl in tests, make its Response body valid JSON.

Example fix

// before
chessApi(path, async () => new Response('<html>blocked</html>', { status: 200 })); // throws here
// after
chessApi(path, async () => new Response(JSON.stringify({ chess_rapid: {} }), { status: 200, headers: { 'content-type': 'application/json' } }));
Defensive patterns

Strategy: retry

Validate before calling

// Check the body is actually JSON before heavy processing:
const resp = await fetch(url, { headers: { accept: 'application/json' } });
const text = await resp.text();
try { JSON.parse(text); } catch { throw new Error(`Non-JSON body from ${url}: ${text.slice(0, 120)}`); }

Type guard

function isMalformedJsonError(e) {
  return e instanceof Error && /malformed JSON/.test(e.message);
}

Try / catch

async function fetchJsonWithRetry(fn, { retries = 2 } = {}) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (e) {
      if (!isMalformedJsonError(e) || i >= retries) throw e;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Prevention

When it happens

Trigger: The Chess.com endpoint or an intermediary (proxy, captive portal, HTML error page from a load balancer) returns HTML or an empty body with a 200 status; a custom fetchImpl stub returns a non-JSON body string.

Common situations: Captive Wi-Fi portals injecting HTML into all responses; corporate proxies replacing error bodies; CDN edge errors behind a 200; test stubs like new Response('ok') without JSON content.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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