jackwener/OpenCLI · error · CommandExecutionError

eastmoney convertible returned invalid JSON: ${error?.messag

Error message

eastmoney convertible returned invalid JSON: ${error?.message ?? error}

What it means

CommandExecutionError thrown (clis/eastmoney/convertible.js:156) when `resp.json()` rejects — i.e. the HTTP response was OK (2xx) but the body is not valid JSON. The original parse error message is embedded. Eastmoney endpoints normally return JSON, so this indicates the body was HTML or otherwise non-JSON despite a success status.

Source

Thrown at clis/eastmoney/convertible.js:156

    const url = new URL('https://push2.eastmoney.com/api/qt/clist/get');
    url.searchParams.set('pn', '1');
    url.searchParams.set('pz', String(limit));
    url.searchParams.set('po', sort.order === 'desc' ? '1' : '0');
    url.searchParams.set('np', '1');
    url.searchParams.set('fltt', '2');
    url.searchParams.set('invt', '2');
    url.searchParams.set('fid', sort.fid);
    url.searchParams.set('fs', 'b:MK0354');
    url.searchParams.set('fields', 'f12,f14,f2,f3,f6,f229,f230,f232,f234,f235,f236,f237,f238,f239,f243');
    url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');

    const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
    if (!resp.ok) throw new CommandExecutionError(`eastmoney convertible failed: HTTP ${resp.status}`);
    let data;
    try {
      data = await resp.json();
    } catch (error) {
      throw new CommandExecutionError(`eastmoney convertible returned invalid JSON: ${error?.message ?? error}`);
    }
    const diff = extractConvertibleDiff(data);

    return mapConvertibleRows(diff, limit);
  },
});

export const __test__ = { SORTS, extractConvertibleDiff, mapConvertibleRow, mapConvertibleRows, parseConvertibleLimit };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw response text to see whether it's HTML (block page) or truncated JSON.
  2. Retry — transient interception often clears on a second request.
  3. Check proxy/VPN/captive-portal interference and try a direct connection.
  4. Send a fuller browser User-Agent to avoid WAF interstitials.
  5. If persistent, inspect whether the endpoint moved and accept a content-type check before parsing.

Example fix

// before
const data = await resp.json();
// after
const text = await resp.text();
let data;
try {
  data = JSON.parse(text);
} catch (e) {
  throw new Error('non-JSON body: ' + text.slice(0, 200));
}
Defensive patterns

Strategy: try-catch

Validate before calling

const ct = resp.headers.get('content-type') || '';
if (!ct.includes('json')) throw new Error(`expected JSON, got ${ct}; likely blocked page`);
const text = await resp.text();

Try / catch

try {
  data = await resp.json();
} catch (error) {
  const body = await resp.text().catch(() => '');
  console.error('non-JSON body preview:', body.slice(0, 200));
  if (/html/i.test(body)) console.error('blocked/interstitial page — change IP or User-Agent');
  return retryWithBackoff(fetchConvertible);
}

Prevention

When it happens

Trigger: The push2 endpoint returns HTTP 200 with an HTML anti-bot/verification page, a captive-portal or proxy interception page, truncated/garbled transfer, or a `charset` mismatch that breaks JSON.parse. Also fires if a middlebox rewrites the response body.

Common situations: WAF soft-blocks that still return 200; hotel/office captive portals intercepting HTTPS through a misconfigured proxy; eastmoney serving a region-block interstitial; network middleboxes stripping content-encoding and corrupting the body.

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