{"record":{"id":"21c71984ebce0de5","repo":"jackwener/OpenCLI","slug":"linkedin-thread-snapshot-returned-a-malformed-api","errorCode":null,"errorMessage":"LinkedIn thread-snapshot returned a malformed API discovery payload.","messagePattern":"LinkedIn thread-snapshot returned a malformed API discovery payload\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/thread-snapshot.js","lineNumber":339,"sourceCode":"    if (discovery && Array.isArray(discovery.apiUrls) && discovery.apiUrls.length === 0) {\n      const firstDiscovery = discovery;\n      await page.wait(4);\n      const retried = unwrapEvaluateResult(await page.evaluate(buildThreadApiDiscoveryScript(0)));\n      if (retried && typeof retried === 'object' && !Array.isArray(retried)) {\n        discovery = {\n          ...retried,\n          scrollAttempts: firstDiscovery.scrollAttempts,\n          scrollStable: firstDiscovery.scrollStable,\n        };\n      } else {\n        discovery = retried;\n      }\n    }\n    if (discovery?.authRequired) {\n      throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn thread-snapshot requires an active signed-in LinkedIn browser session.');\n    }\n    if (!discovery || typeof discovery !== 'object' || Array.isArray(discovery) || !Array.isArray(discovery.apiUrls)) {\n      throw new CommandExecutionError('LinkedIn thread-snapshot returned a malformed API discovery payload.');\n    }\n\n    const actualUrl = canonicalizeLinkedInThreadUrl(discovery.url || '');\n    if (threadUrl && actualUrl && threadUrl !== actualUrl) {\n      throw new CommandExecutionError('LinkedIn thread-snapshot blocked: thread_url_mismatch', `Expected ${threadUrl}; actual ${actualUrl}`);\n    }\n    if (maxScrolls >= 4 && discovery.scrollAttempts >= maxScrolls && discovery.scrollStable === false) {\n      throw new CommandExecutionError('LinkedIn thread history did not stabilize before --max-scrolls; refusing to return a partial snapshot.');\n    }\n\n    const apiUrls = validateThreadApiUrls(discovery.apiUrls, threadUrl);\n    const csrf = await requireLinkedInCookie(page, 'LinkedIn thread-snapshot');\n    const fetched = unwrapEvaluateResult(\n      await page.evaluate(buildFetchThreadPagesScript(apiUrls, csrf)),\n    );\n    if (fetched?.authRequired) {\n      throw new AuthRequiredError(LINKEDIN_DOMAIN, `LinkedIn messengerMessages API authentication failed: ${fetched.error}`);\n    }","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/thread-snapshot.js#L321-L357","documentation":"The discovery step returns the intercepted thread info including the captured apiUrls. The command throws this CommandExecutionError when discovery is null/not an object/is an array, or lacks a valid apiUrls array — i.e. the scraper could not identify any messengerMessages network requests for the open thread.","triggerScenarios":"The page loaded but no matching GraphQL request was captured (network interception failed, wrong URL passed, messaging page failed to render); a retry still returned a malformed shape; discovery returned an unexpected object after LinkedIn's frontend changed its request pattern.","commonSituations":"Slow page load — scrolls finished before the API fired; browser extension or proxy blocking voyager API calls; LinkedIn UI changes so no request matches the messengerMessages pattern; passing a non-messaging URL that never triggers the API.","solutions":["Verify --thread-url is a canonical LinkedIn messaging thread URL (linkedin.com/messaging/thread/...)","Increase wait time / max-scrolls so the API requests actually fire and are captured before parsing","Disable proxies/extensions that might block or rewrite voyager API requests","Update the library if LinkedIn renamed the GraphQL endpoint or queryId pattern"],"exampleFix":"// before: thread URL that never triggers messengerMessages\nawait run('linkedin thread-snapshot', { 'thread-url': 'https://www.linkedin.com/messaging/' });\n// after: exact thread URL\nawait run('linkedin thread-snapshot', { 'thread-url': 'https://www.linkedin.com/messaging/thread/2-Yzg1Zj...' });","handlingStrategy":"validation","validationCode":"// Confirm the URL is a messaging thread URL that triggers the messengerMessages API\nconst u = new URL(threadUrl);\nif (u.hostname !== 'www.linkedin.com' || !u.pathname.startsWith('/messaging/thread/')) {\n  throw new Error('Provide a canonical LinkedIn messaging thread URL');\n}","typeGuard":"const isMessagingThreadUrl = (v) => {\n  try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'www.linkedin.com' && u.pathname.startsWith('/messaging/thread/'); }\n  catch { return false; }\n};","tryCatchPattern":"try {\n  const snap = await run('linkedin thread-snapshot', { 'thread-url': url });\n} catch (err) {\n  if (String(err.message).includes('malformed API discovery payload')) {\n    // no messengerMessages requests captured: increase waits/scrolls and retry\n  } else throw err;\n}","preventionTips":["Pass exact /messaging/thread/... URLs, not the messaging inbox","Give the page enough wait time and scrolls for the API calls to fire","Disable proxies/extensions that block or rewrite voyager API traffic"],"tags":["linkedin","discovery-failed","network-interception"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}