{"record":{"id":"7df82a0af6569324","repo":"jackwener/OpenCLI","slug":"sales-navigator-recipient-urn-must-be-urn-li-fs-sa","errorCode":null,"errorMessage":"Sales Navigator recipient urn must be urn:li:fs_salesProfile:(profileId,authType,authToken)","messagePattern":"Sales Navigator recipient urn must be urn:li:fs_salesProfile:\\(profileId,authType,authToken\\)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-thread.js","lineNumber":39,"sourceCode":"  return host === 'linkedin.com' || host.endsWith('.linkedin.com');\n}\n\nfunction parseSalesProfileUrn(value) {\n  const raw = normalizeWhitespace(value);\n  const match = raw.match(/^urn:li:fs_salesProfile:\\(([^,()]+),([^,()]+),([^,()]+)\\)$/);\n  if (!match) return '';\n  const parts = [match[1], match[2], match[3]].map((part) => normalizeWhitespace(part).toLowerCase());\n  if (parts.some((part) => !part || part === 'undefined' || part === 'null' || part === 'not_available')) return '';\n  return raw;\n}\n\nfunction parseThreadInput(value) {\n  const raw = normalizeWhitespace(value);\n  if (!raw) return ['empty', ''];\n  if (/^2-[A-Za-z0-9+/=_-]+$/.test(raw)) return ['thread_id', raw];\n  if (/^urn:li:fs_salesProfile:\\(/.test(raw)) {\n    const urn = parseSalesProfileUrn(raw);\n    if (!urn) throw new ArgumentError('Sales Navigator recipient urn must be urn:li:fs_salesProfile:(profileId,authType,authToken)');\n    return ['recipient_urn', urn];\n  }\n  try {\n    const url = new URL(raw);\n    if (url.protocol !== 'https:' || url.username || url.password || url.port || !isLinkedInHost(url.hostname)) return ['name', raw.toLowerCase()];\n    const inboxMatch = url.pathname.match(/^\\/sales\\/inbox\\/([^/]+)\\/?$/i);\n    if (inboxMatch) return ['thread_id', decodeURIComponent(inboxMatch[1])];\n    const leadMatch = url.pathname.match(/^\\/sales\\/lead\\/([^,/]+),([^,/]+),([^/]+)\\/?$/i);\n    if (leadMatch) {\n      const urn = `urn:li:fs_salesProfile:(${decodeURIComponent(leadMatch[1])},${decodeURIComponent(leadMatch[2])},${decodeURIComponent(leadMatch[3])})`;\n      if (!parseSalesProfileUrn(urn)) {\n        throw new ArgumentError('Sales Navigator lead URL must contain resolved profileId, authType, and authToken');\n      }\n      return ['recipient_urn', urn];\n    }\n  } catch (err) {\n    if (err instanceof ArgumentError) throw err;\n    // Fall through to name matching for non-URL text.","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-thread.js#L21-L57","documentation":"An ArgumentError from parseThreadInput (clis/linkedin/salesnav-thread.js:39) raised when the user passes a value that starts with 'urn:li:fs_salesProfile:(' but whose payload does not pass parseSalesProfileUrn validation. A valid URN must be exactly urn:li:fs_salesProfile:(profileId,authType,authToken) with three non-empty, comma-separated parts, none of which are 'undefined', 'null', or 'not_available'. This guards against passing placeholder or truncated URNs that would fail downstream API calls.","triggerScenarios":"Passing a recipient URN like 'urn:li:fs_salesProfile:(ACoAABC,)' (missing authToken), a URN containing literal 'undefined'/'null'/'not_available' parts copied from unset object fields, or a URN with stray parentheses/extra commas that breaks the strict three-part regex.","commonSituations":"Copy-pasting a URN from partially rendered JSON or logs where the authToken field was absent; templating a URN from a JS object whose fields were undefined (stringifying to 'undefined'); hand-editing URNs and dropping a segment; older lead_url formats with different segment counts.","solutions":["Inspect the URN and ensure it has exactly three comma-separated segments inside parentheses: (profileId,authType,authToken), with no empty or 'undefined' parts.","Copy the recipient_urn fresh from a salesnav-search output row (its recipient_urn column is already validated) instead of hand-building one.","If you only have a profileId, use the Sales Navigator lead URL form https://www.linkedin.com/sales/lead/<profileId>,<authType>,<authToken> or the person's exact name and let the command resolve it.","Strip surrounding whitespace/quotes and re-check for stray characters like nested parentheses or trailing commas before retrying."],"exampleFix":"// before\nconst urn = `urn:li:fs_salesProfile:(${lead.profileId},undefined,${lead.token})`;\nawait run('linkedin salesnav-thread', [urn]);\n// after (validate parts before building)\nconst parts = [lead.profileId, lead.authType, lead.authToken];\nif (parts.some((p) => !p || p === 'undefined')) throw new Error('incomplete salesProfile parts');\nconst urn = `urn:li:fs_salesProfile:(${parts.join(',')})`;\nawait run('linkedin salesnav-thread', [urn]);","handlingStrategy":"validation","validationCode":"// Validate a recipient URN before calling salesnav-thread:\nfunction isValidSalesProfileUrn(v) {\n  const m = /^urn:li:fs_salesProfile:\\(([^,()]+),([^,()]+),([^,()]+)\\)$/.test(v);\n  const parts = v.slice('urn:li:fs_salesProfile:('.length, -1).split(',');\n  return parts.length === 3 && parts.every((p) => p && !['undefined','null','not_available'].includes(p));\n}\nif (!isValidSalesProfileUrn(urn)) throw new Error('recipient_urn must be (profileId,authType,authToken) with resolved values');","typeGuard":"function isSalesProfileUrn(value) {\n  if (typeof value !== 'string') return false;\n  const m = value.match(/^urn:li:fs_salesProfile:\\(([^,()]+),([^,()]+),([^,()]+)\\)$/);\n  if (!m) return false;\n  return m.slice(1).every((p) => p && !['undefined', 'null', 'not_available'].includes(p));\n}","tryCatchPattern":"try {\n  await run('linkedin salesnav-thread', [urn]);\n} catch (err) {\n  if (err instanceof ArgumentError && /recipient urn/.test(err.message)) {\n    // fall back to resolving by exact name via salesnav-search output\n    return run('linkedin salesnav-thread', [lead.name]);\n  }\n  throw err;\n}","preventionTips":["Always copy recipient_urn from a validated salesnav-search output row rather than hand-building URNs.","Check for 'undefined'/'null' in URN parts — that means a source object field was missing at template time.","When only a profileId is known, resolve the full URN via search instead of guessing authType/authToken."],"tags":["linkedin","argument-validation","urn","sales-navigator"],"backgroundTag":"invalid-urn-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}