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
- Retry the request — transient proxy/CDN glitches often resolve.
- curl the exact URL from the error message and inspect whether the body is HTML or empty.
- Check for captive-portal/proxy interference (try a different network).
- 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
- Set the accept: application/json header (chessApi already does).
- Retry once or twice — edge/CDN glitches are usually transient.
- Check for captive portals or proxies injecting HTML on flaky networks.
- In tests, make mocked Response bodies valid JSON.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- ${label} returned malformed JSON: ${err?.message ?? err}
- ${label} returned malformed JSON: ${err?.message ?? err}
- 12306 ${endpoint} returned an unexpected payload shape
- archive search returned malformed JSON: ${error?.message ||
- archive snapshots returned malformed JSON: ${error?.message
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/4d217d390e583fd0.
Report an issue: GitHub.