jackwener/OpenCLI · error · CommandExecutionError

Sales Navigator messaging thread API returned malformed payl

Error message

Sales Navigator messaging thread API returned malformed payload

What it means

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.

Source

Thrown at clis/linkedin/salesnav-thread.js:87

function participantName(profile) {
  return normalizeWhitespace(profile?.fullName || [profile?.firstName, profile?.lastName].filter(Boolean).join(' '));
}

function participantIndex(thread) {
  const resolution = thread?.participantsResolutionResults || {};
  const participants = Array.isArray(thread?.participants) ? thread.participants : Object.keys(resolution);
  const byUrn = new Map();
  for (const urn of participants) {
    const profile = resolution[urn] || { entityUrn: urn };
    byUrn.set(urn, profile);
  }
  return byUrn;
}

function parseSalesnavThreadMessages(thread) {
  if (!thread || typeof thread !== 'object') {
    throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed payload');
  }
  const threadId = normalizeWhitespace(thread?.id || '');
  if (!threadId) {
    throw new CommandExecutionError('Sales Navigator messaging thread API returned a thread without id');
  }
  if (!Array.isArray(thread?.messages)) {
    throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed messages');
  }
  const byUrn = participantIndex(thread);
  const messages = thread.messages;
  const rows = messages.map((message) => {
    if (!message || typeof message !== 'object') {
      throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed message row');
    }
    const deliveredAt = Number(message?.deliveredAt || 0);
    const senderProfile = byUrn.get(message?.author);
    return {
      message_id: normalizeWhitespace(message?.id || ''),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command; transient API errors or aborted fetches are the most frequent cause.
  2. Confirm the session is valid (Sales Navigator inbox loads in the automation browser); re-authenticate if redirected to login.
  3. Check whether the pinned THREAD_DECORATION version still matches a live /sales-api thread response; refresh it from the network tab if LinkedIn redeployed.
  4. Log the raw fetchSalesnavJson output before parsing to capture what LinkedIn actually returned, then report/update the decoration constant.

Example fix

// before
const thread = await fetchSalesnavJson(page, csrf, url, 'Sales Navigator messaging thread API');
const messages = parseSalesnavThreadMessages(thread);
// after (guard before parse)
const thread = await fetchSalesnavJson(page, csrf, url, 'Sales Navigator messaging thread API');
if (!thread || typeof thread !== 'object') {
  throw new Error('thread API returned non-object; check session/decoration');
}
const messages = parseSalesnavThreadMessages(thread);
Defensive patterns

Strategy: type-guard

Validate before calling

// Guard the thread payload before parsing:
if (!thread || typeof thread !== 'object' || Array.isArray(thread)) {
  throw new Error('thread API returned a non-object payload; check session and decoration version');
}

Type guard

function isThreadObject(t) {
  return typeof t === 'object' && t !== null && !Array.isArray(t);
}

Try / catch

try {
  const messages = parseSalesnavThreadMessages(thread);
} catch (err) {
  if (err instanceof CommandExecutionError && /malformed payload/.test(err.message)) {
    // re-authenticate and refresh decoration before retrying
    await ensureSalesnavSession(page);
    return fetchAndParseThread();
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/77a50af93d3739a8. Report an issue: GitHub.