{"record":{"id":"7b80518f0b99c020","repo":"jackwener/OpenCLI","slug":"sales-navigator-lead-row-missing-profile-identity","errorCode":null,"errorMessage":"Sales Navigator lead row missing profile identity","messagePattern":"Sales Navigator lead row missing profile identity","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-search.js","lineNumber":94,"sourceCode":"function parseLeads(json) {\n  if (!json || typeof json !== 'object' || !Array.isArray(json.elements)) {\n    throw new CommandExecutionError('Sales Navigator lead search API returned malformed payload');\n  }\n  const leads = [];\n  for (const el of json.elements) {\n    if (!el || typeof el !== 'object') {\n      throw new CommandExecutionError('Sales Navigator lead search API returned malformed lead row');\n    }\n    const current = Array.isArray(el.currentPositions) ? el.currentPositions : [];\n    const past = Array.isArray(el.pastPositions) ? el.pastPositions : [];\n    const pos = current[0] || past[0] || {};\n    const name = normalizeWhitespace(el.fullName || [el.firstName, el.lastName].filter(Boolean).join(' '));\n    if (!name) {\n      throw new CommandExecutionError('Sales Navigator lead row missing name');\n    }\n    const entityUrn = normalizeWhitespace(el.entityUrn || '');\n    if (!profileUrlFromEntityUrn(entityUrn)) {\n      throw new CommandExecutionError('Sales Navigator lead row missing profile identity');\n    }\n    leads.push({\n      name,\n      title: normalizeWhitespace(pos.title || ''),\n      company: normalizeWhitespace(pos.companyName || ''),\n      location: normalizeWhitespace(el.geoRegion || ''),\n      degree: normalizeWhitespace(el.degree || ''),\n      profile_url: profileUrlFromEntityUrn(entityUrn),\n      lead_url: leadUrlFromEntityUrn(entityUrn),\n      recipient_urn: entityUrn,\n    });\n  }\n  return leads;\n}\n\nfunction requireLeadSearchResult(result) {\n  if (result?.authRequired) {\n    throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn Sales Navigator API auth failed (HTTP ' + (result.status || '') + '). Confirm the account has Sales Navigator access.');","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-search.js#L76-L112","documentation":"After extracting the name, parseLeads derives the profile URL from el.entityUrn via profileUrlFromEntityUrn (regex fs_salesProfile:(<token>)) and throws CommandExecutionError if no /in/<token> URL can be built. The profile identity is mandatory because downstream commands (e.g. connect) key on it and the output dedupes on profile_url.","triggerScenarios":"A lead row object has an entityUrn that is missing, empty, or does not match /fs_salesProfile:\\(([^,)]+)/ (e.g. a different URN format), so profileUrlFromEntityUrn returns '' and the guard at line 93-95 fires.","commonSituations":"LinkedIn changed the entityUrn format (new URN namespace or extra components) after a Sales Navigator redeploy; rows for company-instead-of-person entities; decorationId bump changing the URN layout; scraping results that include ads or non-member placeholders.","solutions":["Print/log the failing el.entityUrn to confirm the actual URN format LinkedIn now returns.","Update LEAD_SEARCH_DECORATION (line 11) from a live /sales/search/people request if the decoration version changed.","Adjust the regex in profileUrlFromEntityUrn (line 66) to match the new URN layout, or update the library to a version that already does.","Retry with a different keywords query — non-member/placeholder rows may be query-specific.","As a last resort, patch parseLeads to skip rows without a resolvable entityUrn rather than aborting the whole page."],"exampleFix":"// before\nconst entityUrn = normalizeWhitespace(el.entityUrn || '');\nif (!profileUrlFromEntityUrn(entityUrn)) {\n  throw new CommandExecutionError('Sales Navigator lead row missing profile identity');\n}\n// after\nconst entityUrn = normalizeWhitespace(el.entityUrn || '');\nif (!profileUrlFromEntityUrn(entityUrn)) {\n  console.warn('Skipping lead without parsable entityUrn:', entityUrn);\n  continue;\n}","handlingStrategy":"validation","validationCode":"function hasParsableEntityUrn(el) {\n  return typeof el?.entityUrn === 'string'\n    && /fs_salesProfile:\\(([^,)]+)/.test(el.entityUrn);\n}\n// pre-check: const usable = json.elements.filter(hasParsableEntityUrn);","typeGuard":"function isSalesProfileUrn(urn) {\n  return typeof urn === 'string' && /^urn:li:fs_salesProfile:\\([^,()]+,.+\\)$/.test(urn);\n}","tryCatchPattern":"try {\n  const pageLeads = parseLeads(json);\n} catch (e) {\n  if (e.message.includes('missing profile identity')) {\n    // capture a sample URN and compare against the expected fs_salesProfile format\n    console.error('Unparsable entityUrn; sample:', JSON.stringify(json?.elements?.[0]?.entityUrn));\n  } else {\n    throw e;\n  }\n}","preventionTips":["Log entityUrn values once per run to detect URN format drift early.","Refresh LEAD_SEARCH_DECORATION whenever LinkedIn redeploys Sales Navigator (the code comment at lines 8-11 flags this).","Test profileUrlFromEntityUrn against a live response after any library upgrade.","Keep a fallback that skips rows without parsable URNs if partial results are acceptable."],"tags":["api-response","schema-validation","sales-navigator","urn-parsing"],"backgroundTag":"api-response-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}