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, brewFetch calls resp.json(); if the body is not valid JSON (HTML error page, truncated response, proxy interception page) it throws this CommandExecutionError including the underlying JSON parse message. This guards against consuming a response that looked OK but isn't the expected API payload.

Source

Thrown at clis/homebrew/utils.js:88

    }
    if (resp.status === 404) {
        throw new EmptyResultError(label, `Homebrew API returned 404 for ${url}.`);
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'Homebrew throttles bursts; wait a few seconds 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;
}

/** Coerce a count value (which Homebrew analytics serves as `"139,972"`) to a plain number. */
export function parseInstallCount(value) {
    if (value == null) return null;
    const s = String(value).replace(/,/g, '').trim();
    if (!s) return null;
    const n = Number(s);
    return Number.isFinite(n) ? n : null;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw response with curl to see what body actually comes back for that URL.
  2. Bypass or fix the intercepting proxy/captive portal (authenticate, or switch networks).
  3. If using a mirror, ensure it serves the exact JSON files; fix its content-type and body.
  4. Retry once the network path returns real JSON; wrap in try-catch to surface a clear message.

Example fix

// before
const body = await brewFetch(url, 'formula info'); // 200 + HTML -> malformed JSON error
// after
try {
  const body = await brewFetch(url, 'formula info');
} catch (err) {
  if (/malformed JSON/.test(err.message)) {
    const raw = await fetch(url); console.log(await raw.text()); // inspect actual body
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  const body = await brewFetch(url, label);
  return body;
} catch (err) {
  if (err instanceof CommandExecutionError && err.message.includes('malformed JSON')) {
    // proxy/captive-portal or outage page — inspect raw body, switch network, or use cache
    return getCachedOrEmpty(label);
  }
  throw err;
}

Prevention

When it happens

Trigger: A proxy/captive portal returning HTTP 200 with an HTML login page; a CDN/GitHub Pages error page served with 200; truncated response body on flaky connections; pointing brewFetch at a mirror that returns non-JSON content.

Common situations: Hotel/airport Wi-Fi captive portals hijacking requests; SSL-inspection appliances injecting pages; custom BREW_BASE mirrors misconfigured to serve HTML; network middleware in tests returning stub bodies.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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