jackwener/OpenCLI · error · CommandExecutionError

Sales Navigator messaging threads API returned malformed thr

Error message

Sales Navigator messaging threads API returned malformed thread row

What it means

This CommandExecutionError is thrown by parseSalesnavThreads when an individual thread inside json.elements is null or not an object. The overall payload shape was valid, but one row is unusable and would otherwise cause a downstream TypeError.

Source

Thrown at clis/linkedin/salesnav-inbox.js:65

function isSelfParticipant(profile) {
  const degree = String(profile?.degree ?? '').trim();
  return degree === '0';
}

function otherParticipantName(thread) {
  const participants = getThreadParticipants(thread);
  const other = participants.find((p) => !isSelfParticipant(p)) || participants[0];
  return normalizeWhitespace(other?.fullName || [other?.firstName, other?.lastName].filter(Boolean).join(' '));
}

function parseSalesnavThreads(json) {
  if (!json || typeof json !== 'object' || !Array.isArray(json.elements)) {
    throw new CommandExecutionError('Sales Navigator messaging threads API returned malformed payload');
  }
  return json.elements.map((thread) => {
    if (!thread || typeof thread !== 'object') {
      throw new CommandExecutionError('Sales Navigator messaging threads API returned malformed thread row');
    }
    const messages = Array.isArray(thread?.messages) ? thread.messages : [];
    const lastMessage = messages[0] || {};
    const deliveredAt = Number(lastMessage.deliveredAt || thread?.nextPageStartsAt || 0);
    const threadId = normalizeWhitespace(thread?.id || '');
    if (!threadId) {
      throw new CommandExecutionError('Sales Navigator messaging thread row missing id');
    }
    return {
      thread_id: threadId,
      thread_url: salesnavThreadUrl(threadId),
      person_name: otherParticipantName(thread),
      last_message_snippet: normalizeWhitespace(lastMessage.body || lastMessage.subject || '').slice(0, 300),
      last_activity_time: deliveredAt ? new Date(deliveredAt).toISOString() : '',
      unread: Number(thread?.unreadMessageCount || 0) > 0,
      unread_count: Number(thread?.unreadMessageCount || 0),
      total_message_count: Number(thread?.totalMessageCount || messages.length || 0),
      archived: Boolean(thread?.archived),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Update the library to handle the current thread element shape
  2. Filter out non-object elements before parsing: elements.filter(t => t && typeof t === 'object')
  3. Inspect the raw response to see what the malformed entries look like
  4. Retry the request in case of a transient partial response

Example fix

// before
parseSalesnavThreads(json); // throws on any bad row
// after
json.elements = json.elements.filter((t) => t && typeof t === 'object');
parseSalesnavThreads(json);
Defensive patterns

Strategy: validation

Validate before calling

const goodThreads = (json.elements || []).filter((t) => t && typeof t === 'object' && !Array.isArray(t));

Type guard

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

Try / catch

try {
  const rows = await fetchInboxRows(page);
} catch (e) {
  if (String(e.message).includes('malformed thread row')) {
    logger.warn('Skipping bad thread row', { error: e.message });
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: The Sales Nav threads response contains null entries or non-object values inside the elements array — typically from partial/corrupt API responses or schema drift introducing wrapper objects where plain thread objects used to be.

Common situations: Sales Navigator pagination returning truncated/mixed pages; API version change wrapping thread data in an extra layer (so the element is no longer the thread itself); concurrent account access producing malformed entries.

Understand the failure class

Related errors


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