{"record":{"id":"8a5da8a152410360","repo":"jackwener/OpenCLI","slug":"eastmoney-convertible-returned-a-malformed-respons","errorCode":null,"errorMessage":"eastmoney convertible returned a malformed response envelope","messagePattern":"eastmoney convertible returned a malformed response envelope","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/convertible.js","lineNumber":61,"sourceCode":"  throw new CommandExecutionError(`eastmoney convertible returned malformed ${field} for ${bondCode || 'unknown bond'}`);\n}\n\nexport function parseConvertibleLimit(value) {\n  if (value === undefined || value === null || value === '') return 20;\n  if (typeof value === 'number') {\n    if (Number.isInteger(value) && value >= 1 && value <= 100) return value;\n    throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  }\n  const raw = String(value).trim();\n  if (!/^\\d+$/.test(raw)) throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  const parsed = Number(raw);\n  if (parsed < 1 || parsed > 100) throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  return parsed;\n}\n\nexport function extractConvertibleDiff(data) {\n  if (!data || typeof data !== 'object' || Array.isArray(data)) {\n    throw new CommandExecutionError('eastmoney convertible returned a malformed response envelope');\n  }\n  if (!data.data || typeof data.data !== 'object' || Array.isArray(data.data)) {\n    throw new CommandExecutionError('eastmoney convertible returned a malformed data envelope');\n  }\n  const diff = data.data.diff;\n  if (!Array.isArray(diff)) {\n    throw new CommandExecutionError('eastmoney convertible returned malformed diff data');\n  }\n  if (diff.length === 0) {\n    throw new EmptyResultError('eastmoney convertible');\n  }\n  return diff;\n}\n\n// Map a raw eastmoney clist `diff` item to an output row.\n//\n// #2109: f238 / f239 were previously emitted as `remainingYears` / `ytm`, but\n// cross-verification (12/12 fingerprint hits) shows f239 is the putback trigger","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/convertible.js#L43-L79","documentation":"Thrown by extractConvertibleDiff (clis/eastmoney/convertible.js:61) when the value returned from the eastmoney push2 clist API is not a plain object. The library expects the standard eastmoney envelope `{ data: { diff: [...] } }`; anything else (null, an array, a string, HTML text parsed leniently) fails this first envelope check. It is a CommandExecutionError, meaning the remote API response shape violated the contract the CLI relies on.","triggerScenarios":"extractConvertibleDiff(data) is called with data that is null/undefined, a primitive, or an Array — e.g. the push2 endpoint returned `{}`-like content, an HTML error/anti-bot page that somehow parsed, or a proxy stripped the body. Callers: the `diff` step inside the `eastmoney convertible` cli func after `resp.json()` succeeds.","commonSituations":"Eastmoney WAF/anti-bot interception returning a non-API body; region-blocked or rate-limited responses returning empty payloads; a corporate proxy or captive portal injecting an HTML page; eastmoney changing or deprecating the `/api/qt/clist/get` response format; unit tests feeding mock data of the wrong shape.","solutions":["Re-run the command; transient anti-bot blocks often clear on retry (ideally with a browser-like User-Agent).","Verify the endpoint still returns JSON by curling https://push2.eastmoney.com/api/qt/clist/get with the same query params (fs=b:MK0354, ut=...).","Check for proxy/VPN interference that replaces the JSON body with an HTML page.","If eastmoney changed the envelope format, update extractConvertibleDiff to the new shape.","In code, validate the response is a non-array object before calling extractConvertibleDiff."],"exampleFix":"// before\nconst data = await resp.json();\nconst diff = extractConvertibleDiff(data);\n// after\nconst data = await resp.json();\nif (!data || typeof data !== 'object' || Array.isArray(data)) {\n  throw new Error('unexpected eastmoney response, not a JSON object');\n}\nconst diff = extractConvertibleDiff(data);","handlingStrategy":"type-guard","validationCode":"function isPlainObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }\nif (!isPlainObject(response)) throw new Error('eastmoney response is not an object');","typeGuard":"function isValidEnvelope(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const diff = extractConvertibleDiff(data);\n} catch (e) {\n  if (String(e.message).includes('malformed response envelope')) {\n    console.error('eastmoney returned a non-object body — likely blocked or changed API');\n    return fallbackFetch();\n  }\n  throw e;\n}","preventionTips":["Always run extractConvertibleDiff on the parsed JSON, never on raw text.","Check content-type is application/json before parsing.","Log the first ~200 chars of unexpected bodies to diagnose blocks.","Pin/verify the push2 endpoint URL against the live API periodically."],"tags":["api","response-shape","eastmoney","validation"],"backgroundTag":"malformed-api-response-envelope","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}