{"record":{"id":"72c7fb5753574f5b","repo":"jackwener/OpenCLI","slug":"linkedin-messaging-api-returned-a-conversation-wit","errorCode":null,"errorMessage":"LinkedIn messaging API returned a conversation without thread id","messagePattern":"LinkedIn messaging API returned a conversation without thread id","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/inbox.js","lineNumber":92,"sourceCode":"  const participantInfo = (p) => {\n    if (!p) return { name: '', kind: '' };\n    const pt = p.participantType || {};\n    if (pt.organization && pt.organization.name) return { name: norm(pt.organization.name.text), kind: 'organization' };\n    if (pt.member) {\n      const fn = pt.member.firstName && pt.member.firstName.text;\n      const ln = pt.member.lastName && pt.member.lastName.text;\n      return { name: norm([fn, ln].filter(Boolean).join(' ')), kind: 'member' };\n    }\n    if (pt.agent && pt.agent.name) return { name: norm(pt.agent.name.text), kind: 'agent' };\n    return { name: '', kind: '' };\n  };\n\n  const entries = [];\n  for (const conv of included) {\n    if (!conv || conv.$type !== 'com.linkedin.messenger.Conversation') continue;\n    const threadId = String(conv.backendUrn || '').replace(/^urn:li:messagingThread:/, '');\n    if (!threadId) {\n      throw new CommandExecutionError('LinkedIn messaging API returned a conversation without thread id');\n    }\n\n    const others = [];\n    let counterpartyKind = '';\n    for (const urn of conv['*conversationParticipants'] || []) {\n      const p = byUrn.get(urn);\n      if (!p) continue;\n      if (mailboxUrn && p.hostIdentityUrn === mailboxUrn) continue; // exclude the inbox owner\n      const info = participantInfo(p);\n      if (info.name) {\n        others.push(info.name);\n        if (!counterpartyKind) counterpartyKind = info.kind;\n      }\n    }\n\n    const msgUrns = (conv.messages && conv.messages['*elements']) || [];\n    const lastMsg = byUrn.get(msgUrns[0]);\n    let preview = lastMsg && lastMsg.body ? norm(lastMsg.body.text) : '';","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/inbox.js#L74-L110","documentation":"While walking included entities, each com.linkedin.messenger.Conversation must expose a backendUrn of the form urn:li:messagingThread:<id>. If stripping the prefix yields an empty threadId, parseConversations throws CommandExecutionError because thread_id is the primary key for every returned row and its messaging URL.","triggerScenarios":"A conversation entity in normalized.included has no backendUrn (or one with an unexpected URN namespace), which can occur for special/system conversations, sponsored threads, or after a LinkedIn URN format change.","commonSituations":"LinkedIn introducing new conversation types (InMail campaigns, support threads) lacking messagingThread backendUrns; URN prefix change from urn:li:messagingThread: to a new namespace; partially synced inbox returning placeholder conversation entities.","solutions":["Update the CLI — maintainers may add a skip-rule for URN types that cannot yield a thread id.","Retry after reloading /messaging/; placeholder entities from a partial sync usually disappear.","Locally change the throw to `continue` so conversations without backendUrn are skipped instead of aborting the whole listing.","Capture the offending conversation entity and report it so the URN parsing regex can be widened."],"exampleFix":"// before\nif (!threadId) {\n  throw new CommandExecutionError('LinkedIn messaging API returned a conversation without thread id');\n}\n// after\nif (!threadId) continue; // skip system/sponsored conversations without a messagingThread URN","handlingStrategy":"type-guard","validationCode":"function hasThreadUrn(conv) {\n  return conv && conv.$type === 'com.linkedin.messenger.Conversation' &&\n    typeof conv.backendUrn === 'string' && conv.backendUrn.startsWith('urn:li:messagingThread:');\n}\n// filter before parsing: entities.filter(hasThreadUrn)","typeGuard":"function isParsableConversation(entity) {\n  return Boolean(entity && entity.$type === 'com.linkedin.messenger.Conversation' &&\n    /^urn:li:messagingThread:.+/.test(String(entity.backendUrn || '')));\n}","tryCatchPattern":"try {\n  const convs = await opencli.linkedin.inbox({ limit: 40 });\n} catch (e) {\n  if (/conversation without thread id/.test(e.message || '')) {\n    console.warn('Inbox contained a conversation with no messagingThread URN; retrying.');\n    return opencli.linkedin.inbox({ limit: 40 });\n  }\n  throw e;\n}","preventionTips":["Skip non-standard conversation types (sponsored/system) when processing raw entities.","Widen URN parsing if LinkedIn changes the messagingThread namespace.","Handle partial-sync placeholder entities by retrying after a reload.","Keep fixtures updated with all conversation $types your account can return."],"tags":["linkedin","schema-validation","messaging","urn"],"backgroundTag":"missing-response-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}