jackwener/OpenCLI · error · CommandExecutionError

Sales Navigator messaging thread API returned a thread witho

Error message

Sales Navigator messaging thread API returned a thread without id

What it means

Thrown by parseSalesnavThreadMessages (clis/linkedin/salesnav-thread.js:91) when the thread object exists but its id field is missing or whitespace-only after normalization. The id is required to build thread URLs, correlate messages, and anchor each output row's thread_id. The library treats an id-less thread as a malformed response rather than silently returning rows without an identifier.

Source

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

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 || ''),
      thread_id: threadId,
      sender: participantName(senderProfile) || normalizeWhitespace(message?.author || ''),
      sender_urn: normalizeWhitespace(message?.author || ''),
      text: normalizeWhitespace(message?.body || message?.systemMessageContent || ''),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command to rule out a transient partial response.
  2. Verify the THREAD_DECORATION version still matches the live /sales-api/salesApiThreads response shape; refresh from the network tab if LinkedIn changed it.
  3. Ensure you are parsing the thread-detail response (which carries id), not an inbox-list row where the id may live in a different field like entityUrn.
  4. Log the raw thread object (Object.keys(thread)) before parsing to identify the actual id field name and adapt or report the mismatch.

Example fix

// before (parsing an inbox row that stores id elsewhere)
parseSalesnavThreadMessages(inboxRow);
// after (normalize the id before parsing)
const thread = { ...threadDetail, id: threadDetail.id || threadDetail.entityUrn?.split(':').pop() };
parseSalesnavThreadMessages(thread);
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure an id exists before parsing:
const threadId = typeof thread?.id === 'string' ? thread.id.trim() : '';
if (!threadId) throw new Error('thread payload has no id; verify endpoint/decoration');

Type guard

function hasThreadId(t) {
  return t !== null && typeof t === 'object' &&
    typeof t.id === 'string' && t.id.trim().length > 0;
}

Try / catch

try {
  const messages = parseSalesnavThreadMessages(thread);
} catch (err) {
  if (/thread without id/.test(err.message) && thread?.entityUrn) {
    // normalize alternate id field, then retry the parse
    return parseSalesnavThreadMessages({ ...thread, id: thread.entityUrn.split(':').pop() });
  }
  throw err;
}

Prevention

When it happens

Trigger: LinkedIn returned a thread-shaped object whose id was absent (decoration mismatch dropping the id field); fetchSalesnavJson parsed a partial/error payload that still looked like an object; passing a thread object fetched from a different endpoint that uses a different id key (e.g. entityUrn instead of id).

Common situations: A Sales Navigator redeploy renaming or relocating the id field under a new decoration version; reusing this parser on thread objects from a different API (inbox listing vs thread detail) whose id lives in another property; truncated responses due to session limits.

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/af18744531a179f7. Report an issue: GitHub.