{"record":{"id":"17b00e2039ccc46e","repo":"jackwener/OpenCLI","slug":"sales-navigator-lead-row-missing-name","errorCode":null,"errorMessage":"Sales Navigator lead row missing name","messagePattern":"Sales Navigator lead row missing name","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-search.js","lineNumber":90,"sourceCode":"  if (!match) return '';\n  return `https://www.linkedin.com/sales/lead/${encodeURIComponent(match[1])},${encodeURIComponent(match[2])},${encodeURIComponent(match[3])}`;\n}\n\nfunction 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}","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-search.js#L72-L108","documentation":"parseLeads requires every lead row to have a resolvable display name (el.fullName, or firstName+lastName joined) and throws CommandExecutionError when normalizeWhitespace yields an empty string. The library treats a nameless lead row as unusable data since the name is the primary human-identifiable output column.","triggerScenarios":"A json.elements entry is an object but has no fullName, firstName, or lastName (or they are empty/whitespace-only), so the constructed name is '' and the guard at line 89-91 fires.","commonSituations":"Search results including anonymized/redacted profiles (e.g. out-of-network private mode), LinkedIn returning ghost rows for deactivated accounts, restricted profiles where names are hidden, or schema changes renaming the fullName field after a Sales Navigator redeploy.","solutions":["Retry the search; ghost/anonymous rows are often transient and tied to session state.","Sign in to LinkedIn with Sales Navigator access in the automation browser so restricted profile data is served.","Inspect the offending row in devtools (salesApiLeadSearch response) to see which name field changed or is absent.","Patch parseLeads to skip nameless rows instead of throwing if partial results are acceptable to your workflow.","Update the CLI/library in case upstream adapted to a renamed/added name field in the decoration schema."],"exampleFix":"// before\nif (!name) {\n  throw new CommandExecutionError('Sales Navigator lead row missing name');\n}\n// after\nif (!name) {\n  console.warn('Skipping lead row without name:', el.entityUrn);\n  continue; // skip instead of aborting the whole search\n}","handlingStrategy":"validation","validationCode":"function leadHasName(el) {\n  const n = String(el?.fullName || [el?.firstName, el?.lastName].filter(Boolean).join(' ') || '').trim();\n  return n.length > 0;\n}\n// pre-check a page: const usable = json.elements.filter(leadHasName);","typeGuard":"function hasName(el) {\n  return typeof el === 'object' && el !== null\n    && (typeof el.fullName === 'string' && el.fullName.trim() !== ''\n      || (typeof el.firstName === 'string' && el.firstName.trim() !== ''));\n}","tryCatchPattern":"try {\n  const pageLeads = parseLeads(json);\n} catch (e) {\n  if (e.message.includes('missing name')) {\n    console.warn('Nameless lead row skipped; possibly a restricted/anonymized profile');\n  } else {\n    throw e;\n  }\n}","preventionTips":["Use a Sales Navigator-enabled account so restricted profiles still return names.","Prefer narrower keywords queries that surface full member profiles rather than anonymized rows.","Filter rows client-side (fullName/firstName/lastName present) before passing them to strict parsers.","Expect anonymous/ghost rows near result boundaries and treat single-row failures as skippable."],"tags":["api-response","schema-validation","sales-navigator","missing-field"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}