{"record":{"id":"b5cf2e6bf7574e56","repo":"jackwener/OpenCLI","slug":"label-returned-a-malformed-api-envelope","errorCode":null,"errorMessage":"${label} returned a malformed API envelope","messagePattern":"(.+?) returned a malformed API envelope","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/juejin/utils.js","lineNumber":120,"sourceCode":"        );\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'Juejin throttles bursty traffic; wait a few seconds and retry.',\n        );\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    let payload;\n    try {\n        payload = await resp.json();\n    } catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'err_no')) {\n        throw new CommandExecutionError(`${label} returned a malformed API envelope`);\n    }\n    if (payload.err_no !== 0) {\n        throw new CommandExecutionError(`${label} returned err_no ${payload.err_no}: ${payload.err_msg ?? ''}`);\n    }\n    return payload;\n}\n\nexport function readDataArray(payload, label) {\n    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'data')) {\n        throw new CommandExecutionError(`${label} returned a malformed payload`);\n    }\n    if (!Array.isArray(payload.data)) {\n        throw new CommandExecutionError(`${label} returned a non-array data field`);\n    }\n    if (payload.data.length === 0) {\n        throw new EmptyResultError(label, `${label} returned no articles.`);\n    }\n    return payload.data;","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/juejin/utils.js#L102-L138","documentation":"Juejin's REST API wraps results in an envelope `{ err_no, err_msg, data }`. juejinFetch validates that the parsed JSON is a non-array object containing an `err_no` property and throws this CommandExecutionError when the shape doesn't match. It indicates a contract violation: the endpoint replied, but not with the documented Juejin envelope.","triggerScenarios":"The response parsed as JSON but is null, an array, a scalar, or an object without `err_no` — e.g. the endpoint changed its schema, a gateway returned its own JSON error object ( `{message: ...}` ), or the request hit an unexpected Juejin route returning a different envelope.","commonSituations":"Juejin silently changing/redesigning an endpoint's response shape (adapter version drift); a CDN or gateway returning its own JSON `{error: ...}` with 200; hitting a path that belongs to a different Juejin API family with a different envelope; an old cached adapter against a new backend.","solutions":["Log the parsed body to see the actual shape and compare it with the expected `{ err_no, err_msg, data }` envelope.","Update the adapter if Juejin renamed/removed `err_no` — check the calling command's endpoint against current Juejin API docs.","Confirm you are hitting the intended path (a wrong path on the same host can return a different envelope with 200).","Upgrade the opencli juejin adapter if a newer version adapts to the changed schema.","Report/pin the endpoint: if a gateway is substituting its own JSON error, fix the proxy config instead."],"exampleFix":"// before (adapter assumes envelope blindly)\nconst payload = await juejinFetch(path, body, label);\n\n// after (caller detects envelope drift defensively)\nconst payload = await juejinFetch(path, body, label); // throws on bad envelope\nif (!('data' in payload)) throw new Error(`unexpected envelope: ${JSON.stringify(payload).slice(0, 200)}`);","handlingStrategy":"type-guard","validationCode":"null","typeGuard":"function isJuejinEnvelope(payload) {\n  return (\n    payload !== null &&\n    typeof payload === 'object' &&\n    !Array.isArray(payload) &&\n    Object.hasOwn(payload, 'err_no') &&\n    typeof payload.err_no === 'number'\n  );\n}","tryCatchPattern":"try {\n  const payload = await juejinFetch(path, body, label); // envelope already validated internally\n  if (!Array.isArray(payload.data)) throw new Error('envelope ok but data missing');\n} catch (err) {\n  if (err instanceof CommandExecutionError && /malformed API envelope/.test(err.message)) {\n    console.error(`Juejin API contract changed or a gateway answered: ${err.message}. Update/inspect the adapter.`);\n    return;\n  }\n  throw err;\n}","preventionTips":["Treat this error as schema drift: immediately dump the raw response shape and compare to `{ err_no, err_msg, data }`.","Keep the adapter updated; subscribe to Juejin API changes before running long-lived jobs.","Verify the exact endpoint path — a wrong path on the same host can return a 200 with a foreign envelope.","Add an integration test that asserts the envelope shape so drift is caught before production runs."],"tags":["api-schema","envelope","response-validation","contract-mismatch"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}