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

  1. Log the raw response text (resp.text()) before parsing to see what was actually returned
  2. Check whether a proxy/firewall is intercepting requests (curl the same URL and inspect the body)
  3. Verify the request URL/path is a valid current openFDA API endpoint
  4. Retry the request — openFDA occasionally returns non-JSON transient responses; consider a retry with backoff
  5. 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

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


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