{"record":{"id":"3d22558878e01780","repo":"jackwener/OpenCLI","slug":"sales-navigator-messaging-thread-row-missing-id","errorCode":null,"errorMessage":"Sales Navigator messaging thread row missing id","messagePattern":"Sales Navigator messaging thread row missing id","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-inbox.js","lineNumber":72,"sourceCode":"  const participants = getThreadParticipants(thread);\n  const other = participants.find((p) => !isSelfParticipant(p)) || participants[0];\n  return normalizeWhitespace(other?.fullName || [other?.firstName, other?.lastName].filter(Boolean).join(' '));\n}\n\nfunction parseSalesnavThreads(json) {\n  if (!json || typeof json !== 'object' || !Array.isArray(json.elements)) {\n    throw new CommandExecutionError('Sales Navigator messaging threads API returned malformed payload');\n  }\n  return json.elements.map((thread) => {\n    if (!thread || typeof thread !== 'object') {\n      throw new CommandExecutionError('Sales Navigator messaging threads API returned malformed thread row');\n    }\n    const messages = Array.isArray(thread?.messages) ? thread.messages : [];\n    const lastMessage = messages[0] || {};\n    const deliveredAt = Number(lastMessage.deliveredAt || thread?.nextPageStartsAt || 0);\n    const threadId = normalizeWhitespace(thread?.id || '');\n    if (!threadId) {\n      throw new CommandExecutionError('Sales Navigator messaging thread row missing id');\n    }\n    return {\n      thread_id: threadId,\n      thread_url: salesnavThreadUrl(threadId),\n      person_name: otherParticipantName(thread),\n      last_message_snippet: normalizeWhitespace(lastMessage.body || lastMessage.subject || '').slice(0, 300),\n      last_activity_time: deliveredAt ? new Date(deliveredAt).toISOString() : '',\n      unread: Number(thread?.unreadMessageCount || 0) > 0,\n      unread_count: Number(thread?.unreadMessageCount || 0),\n      total_message_count: Number(thread?.totalMessageCount || messages.length || 0),\n      archived: Boolean(thread?.archived),\n      next_page_starts_at: normalizeWhitespace(thread?.nextPageStartsAt || ''),\n      participants: getThreadParticipants(thread).map((p) => ({\n        name: normalizeWhitespace(p.fullName || [p.firstName, p.lastName].filter(Boolean).join(' ')),\n        entity_urn: normalizeWhitespace(p.entityUrn || ''),\n        object_urn: normalizeWhitespace(p.objectUrn || ''),\n        degree: normalizeWhitespace(p.degree ?? ''),\n      })),","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-inbox.js#L54-L90","documentation":"This CommandExecutionError is thrown by parseSalesnavThreads when a thread row parses successfully but has no usable id (thread?.id is missing or whitespace). Thread IDs are required to build thread URLs and paginate, so rows without one are rejected rather than emitted with broken links.","triggerScenarios":"A valid thread object in json.elements lacks the id field — schema change moving the id to a nested location (e.g. entity object), or a placeholder/empty thread returned by the API.","commonSituations":"Sales Navigator API version updates renaming the id field; thread rows representing system/special threads without stable ids; partially hydrated responses from aggressive pagination.","solutions":["Update the library to read the id from its new location if the schema changed","Skip threads without ids instead of failing the whole batch (filter before mapping)","Inspect the raw payload to find where the thread identifier now lives","Retry with a smaller page size to avoid partially hydrated rows"],"exampleFix":"// before\nif (!threadId) throw new CommandExecutionError('...missing id');\n// after\nif (!threadId) return null; // caller: rows.filter(Boolean)\nconst threadId = normalizeWhitespace(thread?.id || thread?.entity?.id || '');","handlingStrategy":"validation","validationCode":"const usable = (json.elements || []).filter((t) => t && typeof t === 'object' && String(t.id || '').trim() !== '');","typeGuard":"function hasThreadId(t) {\n  return !!t && typeof t === 'object' && typeof t.id === 'string' && t.id.trim() !== '';\n}","tryCatchPattern":"try {\n  const rows = await fetchInboxRows(page);\n} catch (e) {\n  if (String(e.message).includes('missing id')) {\n    logger.warn('Thread without id encountered; check Sales Nav schema');\n    return [];\n  }\n  throw e;\n}","preventionTips":["Filter out rows without ids before parsing","Inspect raw payloads for where the id moved after API updates","Log thread ids as early warnings during schema drift","Version-lock the library against tested Sales Nav responses"],"tags":["linkedin","sales-navigator","missing-identifier"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}