jackwener/OpenCLI · error · CommandExecutionError
${label} returned non-JSON body: ${err.message}
Error message
${label} returned non-JSON body: ${err.message} What it means
openfdaFetch is the shared HTTP helper for openFDA CLI commands. After an HTTP 200 response, it calls resp.json(); if the body cannot be parsed as JSON it wraps the underlying parse error in a CommandExecutionError so CLI failures stay uniform. This guards against openFDA (or an intermediate proxy) returning HTML error pages, empty bodies, or malformed JSON despite a success status.
Source
Thrown at clis/openfda/utils.js:47
resp = await fetch(url, { headers: { 'User-Agent': UA, accept: 'application/json' } });
} catch (err) {
throw new CommandExecutionError(`${label} request failed: ${err.message}`);
}
if (resp.status === 404) {
// openFDA returns 404 for "no matches" instead of an empty results array.
throw new EmptyResultError(label, `${label} returned 404 (no matches).`);
}
if (resp.status === 429) {
throw new CommandExecutionError(`${label} rate-limited (HTTP 429); 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 non-JSON body: ${err.message}`);
}
return body;
}
// openFDA returns most string fields as `[string]` arrays — collapse to first
// element. Preserves `null` (not coerced to empty string) when the slot is
// missing entirely.
export function firstOrNull(arr) {
if (!Array.isArray(arr) || !arr.length) return null;
const v = arr[0];
if (typeof v !== 'string') return v ?? null;
const trimmed = v.trim();
return trimmed.length ? trimmed : null;
}
// Comma-join an array of strings, preserving null when empty.
export function joinOrNull(arr, max = 5) {
if (!Array.isArray(arr) || !arr.length) return null;View on GitHub (pinned to 49907e53dc)
Solutions
- Log the raw response text (resp.text()) before parsing to see what was actually returned
- Check whether a proxy/firewall is intercepting requests (curl the same URL and inspect the body)
- Verify the request URL/path is a valid current openFDA API endpoint
- Retry the request — openFDA occasionally returns non-JSON transient responses; consider a retry with backoff
- Ensure a proper User-Agent/Accept header is sent so the API returns JSON
Example fix
// before
let body;
try {
body = await resp.json();
} catch (err) {
throw new CommandExecutionError(`${label} returned non-JSON body: ${err.message}`);
}
// after
let body;
const raw = await resp.text();
try {
body = JSON.parse(raw);
} catch (err) {
throw new CommandExecutionError(`${label} returned non-JSON body: ${err.message} (first 200 chars: ${raw.slice(0, 200)})`);
} Defensive patterns
Strategy: try-catch
Validate before calling
const resp = await fetch(url);
const ct = resp.headers.get('content-type') || '';
if (!ct.includes('application/json')) {
const raw = await resp.text();
throw new Error(`Expected JSON, got ${ct}: ${raw.slice(0, 200)}`);
} Type guard
function isJsonObject(v) {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try {
const body = await openfdaFetch(url, 'openFDA lookup');
} catch (err) {
if (err instanceof CommandExecutionError && err.message.includes('non-JSON body')) {
console.error('API/proxy returned non-JSON payload; check network/proxy and retry.');
} else {
throw err;
}
} Prevention
- Check the content-type header before parsing JSON
- Log a snippet of the raw body on parse failure to diagnose proxies/HTML pages
- Implement retry with backoff for transient non-JSON responses
- Bypass or configure proxies when testing (curl the URL directly)
- Send Accept: application/json and a descriptive User-Agent
When it happens
Trigger: resp.json() rejects because the response body is empty, HTML (e.g. an error page or rate-limit page from a proxy/CDN), truncated, or otherwise invalid JSON, even though resp.status was OK.
Common situations: Corporate proxies or VPN captive portals injecting HTML; openFDA returning an unexpected content-type or empty body on transient upstream failures; hitting a wrong/legacy endpoint URL; TLS-intercepting middleboxes rewriting responses.
Related errors
- archive search failed: HTTP ${resp.status}
- Bilibili conclusion API returned malformed model_result JSON
- coingecko global returned malformed JSON: ${err?.message ??
- coingecko returned error: ${data.error}
- coingecko top
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a366d7bb83489257.
Report an issue: GitHub.