jackwener/OpenCLI · error · CommandExecutionError
${label} returned malformed JSON: ${err?.message ?? err}
Error message
${label} returned malformed JSON: ${err?.message ?? err} What it means
restCountriesFetch throws CommandExecutionError when resp.json() fails, i.e. the HTTP 200 body is not parseable JSON. The library expects REST Countries to always return JSON on success, so a non-JSON body indicates the response was intercepted or corrupted.
Source
Thrown at clis/rest-countries/utils.js:77
`${label} request failed: ${err?.message ?? err}`,
'Check that restcountries.com is reachable from this network.',
);
}
if (resp.status === 404) {
throw new EmptyResultError(label, `REST Countries returned 404 for ${url}.`);
}
if (resp.status === 429) {
throw new CommandExecutionError(`${label} returned HTTP 429 (rate limited)`);
}
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;
}
/** Convert REST Countries' `{cur: {name, symbol}}` map to a comma-joined list. */
export function joinCurrencies(currencies) {
if (!currencies || typeof currencies !== 'object') return '';
return Object.entries(currencies)
.map(([code, info]) => {
const name = info && typeof info.name === 'string' ? info.name : '';
return name ? `${code} (${name})` : code;
})
.join(', ');
}
/** Convert `{eng: 'English', fra: 'French'}` map to a comma-joined list of language names. */
export function joinLanguages(languages) {
if (!languages || typeof languages !== 'object') return '';View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the request — truncation/corruption is often transient
- Check whether a proxy, VPN, or captive portal is rewriting responses (fetch the URL in a browser and inspect the body)
- Disable intercepting extensions or exclusion-list restcountries.com
- Report a bug if the API persistently returns non-JSON
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
try {
await restCountriesCommand(args);
} catch (e) {
if (String(e.message).includes('malformed JSON')) {
// likely a proxy/captive portal; surface network context to the user
throw new Error('Non-JSON response from restcountries.com — check proxy/VPN/network', { cause: e });
}
throw e;
} Prevention
- Test restcountries.com in a browser when on corporate networks
- Exclude the API domain from proxies/interceptors
- Retry once on JSON parse failures (truncation is often transient)
- Pin to the documented API version
When it happens
Trigger: The API returns status 200 but the body is HTML (captive portal, proxy block page, Cloudflare interstitial), a truncated body, or empty content, causing JSON.parse inside resp.json() to reject.
Common situations: Corporate proxy or Wi-Fi captive portal replacing the response with an HTML page; aggressive ad-blockers/privacy tools intercepting the request; network interruption truncating the body; REST Countries serving an error page with a 200 status.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- ${label} returned invalid JSON
- Xiaoyuzhou refresh returned invalid JSON: ${getErrorMessage(
- 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/e3a155ba287c8e96.
Report an issue: GitHub.