{"record":{"id":"77a50af93d3739a8","repo":"jackwener/OpenCLI","slug":"sales-navigator-messaging-thread-api-returned-malf","errorCode":null,"errorMessage":"Sales Navigator messaging thread API returned malformed payload","messagePattern":"Sales Navigator messaging thread API returned malformed payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-thread.js","lineNumber":87,"sourceCode":"\nfunction participantName(profile) {\n  return normalizeWhitespace(profile?.fullName || [profile?.firstName, profile?.lastName].filter(Boolean).join(' '));\n}\n\nfunction participantIndex(thread) {\n  const resolution = thread?.participantsResolutionResults || {};\n  const participants = Array.isArray(thread?.participants) ? thread.participants : Object.keys(resolution);\n  const byUrn = new Map();\n  for (const urn of participants) {\n    const profile = resolution[urn] || { entityUrn: urn };\n    byUrn.set(urn, profile);\n  }\n  return byUrn;\n}\n\nfunction parseSalesnavThreadMessages(thread) {\n  if (!thread || typeof thread !== 'object') {\n    throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed payload');\n  }\n  const threadId = normalizeWhitespace(thread?.id || '');\n  if (!threadId) {\n    throw new CommandExecutionError('Sales Navigator messaging thread API returned a thread without id');\n  }\n  if (!Array.isArray(thread?.messages)) {\n    throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed messages');\n  }\n  const byUrn = participantIndex(thread);\n  const messages = thread.messages;\n  const rows = messages.map((message) => {\n    if (!message || typeof message !== 'object') {\n      throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed message row');\n    }\n    const deliveredAt = Number(message?.deliveredAt || 0);\n    const senderProfile = byUrn.get(message?.author);\n    return {\n      message_id: normalizeWhitespace(message?.id || ''),","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-thread.js#L69-L105","documentation":"Thrown by parseSalesnavThreadMessages (clis/linkedin/salesnav-thread.js:87) when the thread payload passed from the Sales Navigator messaging API is null or not a plain object. The parser expects the decoded REST decoration object containing id, messages, and participant data. This is a defensive schema check indicating the API returned something structurally different from what the pinned decoration (THREAD_DECORATION) produces.","triggerScenarios":"fetchSalesnavJson returned null/undefined because the page.evaluate wrapper was unwrapped to nothing; LinkedIn returned an error body or empty object instead of a thread; the decorationId version changed so the response no longer matches the expected shape and downstream unwrapping yields a non-object.","commonSituations":"LinkedIn Sales Navigator redeploy changing response decoration (the file comments warn decoration versions get bumped); expired/limited session causing an HTML or error JSON body to be parsed as a thread; passing the result of a failed fetch (null) straight into parsing.","solutions":["Re-run the command; transient API errors or aborted fetches are the most frequent cause.","Confirm the session is valid (Sales Navigator inbox loads in the automation browser); re-authenticate if redirected to login.","Check whether the pinned THREAD_DECORATION version still matches a live /sales-api thread response; refresh it from the network tab if LinkedIn redeployed.","Log the raw fetchSalesnavJson output before parsing to capture what LinkedIn actually returned, then report/update the decoration constant."],"exampleFix":"// before\nconst thread = await fetchSalesnavJson(page, csrf, url, 'Sales Navigator messaging thread API');\nconst messages = parseSalesnavThreadMessages(thread);\n// after (guard before parse)\nconst thread = await fetchSalesnavJson(page, csrf, url, 'Sales Navigator messaging thread API');\nif (!thread || typeof thread !== 'object') {\n  throw new Error('thread API returned non-object; check session/decoration');\n}\nconst messages = parseSalesnavThreadMessages(thread);","handlingStrategy":"type-guard","validationCode":"// Guard the thread payload before parsing:\nif (!thread || typeof thread !== 'object' || Array.isArray(thread)) {\n  throw new Error('thread API returned a non-object payload; check session and decoration version');\n}","typeGuard":"function isThreadObject(t) {\n  return typeof t === 'object' && t !== null && !Array.isArray(t);\n}","tryCatchPattern":"try {\n  const messages = parseSalesnavThreadMessages(thread);\n} catch (err) {\n  if (err instanceof CommandExecutionError && /malformed payload/.test(err.message)) {\n    // re-authenticate and refresh decoration before retrying\n    await ensureSalesnavSession(page);\n    return fetchAndParseThread();\n  }\n  throw err;\n}","preventionTips":["Verify the session is live (inbox loads) before fetching thread payloads.","After LinkedIn redeploys, re-capture THREAD_DECORATION from the network tab and update the constant.","Log raw fetchSalesnavJson output when parsing fails so the actual payload is preserved for diagnosis."],"tags":["linkedin","sales-navigator","api-response","schema-validation","malformed-payload"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}