{"record":{"id":"3e88994c48b544a2","repo":"jackwener/OpenCLI","slug":"eastmoney-convertible-returned-a-malformed-data-en","errorCode":null,"errorMessage":"eastmoney convertible returned a malformed data envelope","messagePattern":"eastmoney convertible returned a malformed data envelope","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/convertible.js","lineNumber":64,"sourceCode":"export 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\n// price (= convPrice × 0.7) and f238 is the pure-bond premium %. Real YTM /\n// remaining term are not in this response's `fields`; adding the correct f-codes\n// is a follow-up that needs a live push2 field dump cross-checked against jisilu.","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/convertible.js#L46-L82","documentation":"Thrown by extractConvertibleDiff (clis/eastmoney/convertible.js:64) when the top-level object parsed from the eastmoney push2 response lacks a valid `data` sub-object (missing, null, a primitive, or an array). The push2 clist API normally nests the result rows at `data.diff`, so the CLI requires `data` to be a plain object before reading `.diff`. This is a CommandExecutionError indicating the API contract changed or the response was truncated/empty.","triggerScenarios":"The eastmoney endpoint returns `{}` or `{ rc:..., data: null }` — typical when the `fs=b:MK0354` board has no data for the query, the `ut` token is rejected, or the request was rate-limited into an empty success (HTTP 200 with an empty payload).","commonSituations":"Calling with sort/limit combinations the backend refuses silently; eastmoney rotating or invalidating the hard-coded `ut` token; regional restrictions returning empty envelopes; scraping from a datacenter IP that gets soft-blocked with an empty 200.","solutions":["Retry the request; soft-empty responses are frequently transient.","Check HTTP status was 200 and log the raw body to confirm `data` is genuinely absent rather than an auth rejection.","Verify the `ut` token and `fs=b:MK0354` query params are still valid against the live push2 API.","Try a different network/User-Agent if a soft block is suspected.","Guard in caller code: check `data && data.data && typeof data.data === 'object'` before extracting diff."],"exampleFix":"// before\nconst diff = extractConvertibleDiff(await resp.json());\n// after\nconst json = await resp.json();\nif (!json?.data || typeof json.data !== 'object' || Array.isArray(json.data)) {\n  throw new Error('eastmoney response missing data envelope: ' + JSON.stringify(json).slice(0, 200));\n}\nconst diff = extractConvertibleDiff(json);","handlingStrategy":"validation","validationCode":"if (!json || typeof json !== 'object' || Array.isArray(json) || !json.data || typeof json.data !== 'object' || Array.isArray(json.data)) {\n  throw new Error('eastmoney response missing data envelope');\n}","typeGuard":"function hasDataEnvelope(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v) &&\n    v.data !== null && typeof v.data === 'object' && !Array.isArray(v.data);\n}","tryCatchPattern":"try {\n  const diff = extractConvertibleDiff(json);\n} catch (e) {\n  if (String(e.message).includes('malformed data envelope')) {\n    console.warn('empty/soft-blocked response, retrying once');\n    return retryWithBackoff(fetchConvertible);\n  }\n  throw e;\n}","preventionTips":["Treat HTTP 200 + empty `data` as a possible soft block and retry.","Keep the `ut` token and fs=b:MK0354 params up to date.","Avoid datacenter IPs that eastmoney soft-blocks with empty payloads.","Assert the envelope shape in integration tests with recorded fixtures."],"tags":["api","response-shape","eastmoney","empty-payload"],"backgroundTag":"malformed-api-response-envelope","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}