{"record":{"id":"6626d1176d192955","repo":"jackwener/OpenCLI","slug":"boss-api-returned-malformed-response","errorCode":null,"errorMessage":"Boss API returned malformed response","messagePattern":"Boss API returned malformed response","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/boss/utils.js","lineNumber":142,"sourceCode":"        };\n        xhr.onerror = () => reject(new Error('Network Error'));\n        xhr.ontimeout = () => reject(new Error('Timeout'));\n        xhr.send(${body ? JSON.stringify(body) : 'null'});\n      });\n    }\n  `;\n    let data;\n    try {\n        data = await page.evaluate(script);\n    } catch (error) {\n        if (error instanceof AuthRequiredError || error instanceof CommandExecutionError) {\n            throw error;\n        }\n        const message = error instanceof Error ? error.message : String(error);\n        throw new CommandExecutionError(`Boss API request failed: ${message}`);\n    }\n    if (!data || typeof data !== 'object') {\n        throw new CommandExecutionError('Boss API returned malformed response');\n    }\n    // Auto-check auth unless caller opts out\n    if (!opts.allowNonZero && data.code !== 0) {\n        assertOk(data);\n    }\n    return data;\n}\n// ── Convenience helpers ─────────────────────────────────────────────────────\n/**\n * Fetch the boss friend (chat) list.\n */\nexport async function fetchFriendList(page, opts = {}) {\n    const pageNum = opts.pageNum ?? 1;\n    const jobId = opts.jobId ?? '0';\n    const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;\n    const data = await bossFetch(page, url, { allowNonZero: opts.allowNonZero });\n    if (opts.allowNonZero && data.code !== 0) return data;\n    const list = data.zpData?.friendList;","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/boss/utils.js#L124-L160","documentation":"After the XHR resolves, bossFetch verifies the parsed payload is a non-null object before reading data.code; a null, undefined, array-less primitive, or empty result throws CommandExecutionError 'Boss API returned malformed response' (no prefix, unlike the assertOk variant). This guards against BOSS endpoints replying 200 with an empty or non-JSON-coercible body.","triggerScenarios":"wapi endpoint returns an empty body or a body that JSON.parse yields null/a primitive (e.g. bare string/number), so page.evaluate resolves with a non-object and the shape check at utils.js:141 fails.","commonSituations":"BOSS serving blank responses during maintenance or under risk-control; CDN edge returning 200 with empty payload; calling a deprecated/renamed wapi endpoint that no longer returns JSON.","solutions":["Retry after a short delay — empty 200 bodies are often transient server-side hiccups.","Re-login in the driven Chrome to rule out an interstitial page replacing the JSON payload.","Verify the endpoint URL/version is current (BOSS may have changed the wapi path); update the library if so.","Capture the raw responseText (via OPENCLI_VERBOSE or a custom page.evaluate) to diagnose what was actually returned.","Wrap the call in retry-with-backoff for robustness against transient blanks."],"exampleFix":"// before\nconst data = await bossFetch(page, url);\n// after\nconst data = await retry(() => bossFetch(page, url), { attempts: 3, delayMs: 1000 });","handlingStrategy":"retry","validationCode":"function looksLikeBossEnvelope(v) {\n  return !!v && typeof v === 'object' && typeof v.code === 'number';\n}\nif (!looksLikeBossEnvelope(await bossFetch(page, probeUrl, { allowNonZero: true }))) {\n  console.error('BOSS returning empty payloads; wait or re-login.');\n}","typeGuard":"function isNonEmptyObject(v) {\n  return typeof v === 'object' && v !== null && Object.keys(v).length > 0;\n}","tryCatchPattern":"try {\n  return await bossFetch(page, url);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /malformed response/.test(e.message)) {\n    await sleep(3000);\n    return bossFetch(page, url);\n  }\n  throw e;\n}","preventionTips":["Retry with backoff; empty 200 bodies are usually transient","Re-login to rule out interstitial pages replacing JSON","Verify the wapi endpoint path is still current; update the library if BOSS changed it","Capture raw responseText to diagnose persistent blanks"],"tags":["malformed-response","empty-body","api-shape"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}