jackwener/OpenCLI · error · CommandExecutionError

${label} returned malformed JSON: ${err?.message ?? err}

Error message

${label} returned malformed JSON: ${err?.message ?? err}

What it means

juejinFetch parses the response with `resp.json()` and wraps any parse failure as a CommandExecutionError. This means the HTTP layer succeeded but the body was not valid JSON (truncated HTML, a WAF/challenge page, gzip corruption, or an empty 2xx body). The underlying SyntaxError message is appended for diagnosis.

Source

Thrown at clis/juejin/utils.js:117

        throw new CommandExecutionError(
            `${label} request failed: ${err?.message ?? err}`,
            'Check that api.juejin.cn is reachable from this network.',
        );
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'Juejin throttles bursty traffic; wait a few seconds and retry.',
        );
    }
    if (!resp.ok) {
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
    }
    let payload;
    try {
        payload = await resp.json();
    } catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'err_no')) {
        throw new CommandExecutionError(`${label} returned a malformed API envelope`);
    }
    if (payload.err_no !== 0) {
        throw new CommandExecutionError(`${label} returned err_no ${payload.err_no}: ${payload.err_msg ?? ''}`);
    }
    return payload;
}

export function readDataArray(payload, label) {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'data')) {
        throw new CommandExecutionError(`${label} returned a malformed payload`);
    }
    if (!Array.isArray(payload.data)) {
        throw new CommandExecutionError(`${label} returned a non-array data field`);
    }
    if (payload.data.length === 0) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Look at the appended parse-error message; if it mentions '<' or unexpected token, dump the raw body (`curl ... | head -c 500`) to see what was actually returned.
  2. Check for a proxy/VPN/WAF in the path returning HTML instead of the API response and bypass or fix it.
  3. Simply retry — truncated bodies from flaky networks are usually transient.
  4. If a proxy strips/rewrites content, set NO_PROXY for api.juejin.cn.
  5. Verify the endpoint still returns JSON (API change) by calling it manually with curl.

Example fix

// before (raw body never inspected)
const payload = await juejinFetch('/recommend_api/feed/v1', body, 'juejin recommend');

// after (caller-side diagnosis when this error repeats)
const resp = await fetch('https://api.juejin.cn/recommend_api/feed/v1', init);
const text = await resp.text();
try { JSON.parse(text); } catch { console.error('Non-JSON body:', text.slice(0, 300)); }
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

function isMalformedJsonError(err) {
  return err instanceof CommandExecutionError && /malformed JSON/.test(err.message);
}

Try / catch

async function fetchJsonWithRetry(path, body, label, attempts = 3) {
  for (let i = 0; ; i++) {
    try { return await juejinFetch(path, body, label); }
    catch (err) {
      if (!isMalformedJsonError(err) || i >= attempts - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * (i + 1))); // truncated bodies are often transient
    }
  }
}

Prevention

When it happens

Trigger: A 2xx response from api.juejin.cn whose body `JSON.parse` rejects: an anti-bot HTML interstitial, a proxy's error page, a truncated/chunked-transfer response, or content-type confusion returning text/html instead of application/json.

Common situations: Captive portals / hotel Wi-Fi intercepting HTTPS (rare, but misconfigured proxies do it); middleboxes injecting HTML into responses; Juejin serving a CAPTCHA page with 200; network flakiness truncating the body mid-download.

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/51e1aa70c12385ea. Report an issue: GitHub.