jackwener/OpenCLI · error · CommandExecutionError

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

Error message

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

What it means

After a 2xx response, wikidataFetch calls resp.json(). If the body cannot be parsed as JSON (truncated response, HTML error page from an intermediary, wrong content-type), it throws a CommandExecutionError noting the label and the underlying parse error.

Source

Thrown at clis/wikidata/utils.js:89

    }
    if (resp.status === 404) {
        throw new EmptyResultError(label, `Wikidata returned 404 for ${url}.`);
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'Wikidata throttles anonymous traffic; back off 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;
}

/**
 * Pick a localised label / description from a `{<lang>: {value}}` map.
 * Falls back to English if the requested language is missing.
 */
export function pickLocalised(map, language) {
    if (!map || typeof map !== 'object') return null;
    const direct = map[language];
    if (direct && typeof direct.value === 'string' && direct.value.trim()) return direct.value;
    if (language !== 'en' && map.en && typeof map.en.value === 'string' && map.en.value.trim()) {
        return map.en.value;
    }
    return null;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log resp status and a snippet of the raw text to see what was actually returned
  2. Verify network path — bypass proxies/VPNs and retry
  3. Retry the request; truncated responses are often transient
  4. Confirm the URL passed to wikidataFetch is a Wikidata API endpoint (.json / api.php)

Example fix

// before
const body = await wikidataFetch(url, 'entity');
// after
try {
  const body = await wikidataFetch(url, 'entity');
} catch (err) {
  if (String(err.message).includes('malformed JSON')) {
    const raw = await fetch(url); console.log(await raw.text()); // inspect actual body
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Type guard

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

Try / catch

try {
  const body = await wikidataFetch(url, label);
} catch (err) {
  if (String(err.message).includes('malformed JSON')) {
    console.error('non-JSON body — check proxy/captive portal; retrying may help');
    throw err;
  }
  throw err;
}

Prevention

When it happens

Trigger: An intermediary (proxy, captive portal, anti-bot page) returning HTML instead of the JSON API response; the connection dropping mid-body so JSON is truncated; a misconfigured URL pointing to a non-API endpoint that returns text/HTML.

Common situations: Corporate proxies or Wi-Fi captive portals injecting HTML login pages; inconsistent networking in containers/CI truncating large responses; fetching through a caching layer that serves an error page with 200.

Understand the failure class

Related errors


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