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 successful HTTP response, openalexFetch calls resp.json(); if the body is not parseable JSON it throws this CommandExecutionError. It guards against proxies, captive portals, or upstream outages returning HTML error pages instead of JSON.

Source

Thrown at clis/openalex/utils.js:116

            'OpenAlex throttles unauthenticated traffic; wait a few seconds and retry, or set OPENALEX_MAILTO.',
        );
    }
    if (!resp.ok) {
        let detail = '';
        try {
            const text = await resp.text();
            const match = text.match(/"message"\s*:\s*"([^"]+)"/);
            if (match) detail = ` (${match[1]})`;
        }
        catch { /* ignore */ }
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}${detail}`);
    }
    let body;
    try {
        body = await resp.json();
    }
    catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    return body;
}

/** Strip the `https://openalex.org/` prefix if present so columns surface just the bare id. */
export function bareId(value) {
    const s = String(value ?? '').trim();
    if (!s) return '';
    return s.replace(/^https?:\/\/(?:api\.)?openalex\.org\//i, '').replace(/^works\//i, '');
}

/** Strip the `https://doi.org/` prefix so DOIs render as plain `10.…/…` strings. */
export function bareDoi(value) {
    const s = String(value ?? '').trim();
    if (!s) return '';
    return s.replace(/^https?:\/\/(?:dx\.)?doi\.org\//i, '');
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Print the first few hundred bytes of the raw response (curl the same URL) to confirm what is actually being returned.
  2. Check whether a proxy or captive portal is intercepting api.openalex.org and bypass/authenticate it.
  3. Retry the request; a truncated body is often transient.
  4. Pin traffic to https and ensure no HTTP_PROXY env var is rewriting openalex requests unexpectedly.

Example fix

// before
const body = await openalexFetch(url, 'openalex works'); // throws on HTML body
// after
try {
  const body = await openalexFetch(url, 'openalex works');
} catch (e) {
  const raw = await fetch(url).then(r => r.text());
  console.error('raw body head:', raw.slice(0, 200));
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function isReachableApi() {
  try {
    const r = await fetch('https://api.openalex.org/works?per-page=1');
    return /json/.test(r.headers.get('content-type') ?? '');
  } catch { return false; }
}

Type guard

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

Try / catch

try {
  const body = await openalexFetch(url, 'openalex works');
} catch (e) {
  if (/malformed JSON/.test(e.message)) {
    // inspect raw body, check proxy/captive portal, retry once
  } else throw e;
}

Prevention

When it happens

Trigger: api.openalex.org (or an intercepting middlebox) returns a 200 response whose body is HTML or truncated — e.g. a Wi-Fi captive portal, a corporate proxy injecting a notice page, or a partially delivered response.

Common situations: Working behind a corporate proxy that rewrites responses; on public Wi-Fi where DNS is hijacked to a login page; a CDN/edge failure serving an HTML error with status 200.

Understand the failure class

Related errors


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