jackwener/OpenCLI · error · CommandExecutionError

Sales Navigator lead search API returned malformed lead row

Error message

Sales Navigator lead search API returned malformed lead row

What it means

parseLeads validates each entry of the Sales Navigator lead search response's json.elements array and throws CommandExecutionError when an element is null or not an object. This indicates the API returned a payload whose shape deviates from the expected lead-row schema (each element should be an object with fields like fullName, entityUrn, currentPositions). The library throws eagerly rather than emitting garbage rows.

Source

Thrown at clis/linkedin/salesnav-search.js:83

function profileUrlFromEntityUrn(entityUrn) {
  const match = String(entityUrn || '').match(/fs_salesProfile:\(([^,)]+)/);
  return match && match[1] ? 'https://www.linkedin.com/in/' + match[1] : '';
}

function leadUrlFromEntityUrn(entityUrn) {
  const match = String(entityUrn || '').match(/^urn:li:fs_salesProfile:\(([^,()]+),([^,()]+),([^,()]+)\)$/);
  if (!match) return '';
  return `https://www.linkedin.com/sales/lead/${encodeURIComponent(match[1])},${encodeURIComponent(match[2])},${encodeURIComponent(match[3])}`;
}

function parseLeads(json) {
  if (!json || typeof json !== 'object' || !Array.isArray(json.elements)) {
    throw new CommandExecutionError('Sales Navigator lead search API returned malformed payload');
  }
  const leads = [];
  for (const el of json.elements) {
    if (!el || typeof el !== 'object') {
      throw new CommandExecutionError('Sales Navigator lead search API returned malformed lead row');
    }
    const current = Array.isArray(el.currentPositions) ? el.currentPositions : [];
    const past = Array.isArray(el.pastPositions) ? el.pastPositions : [];
    const pos = current[0] || past[0] || {};
    const name = normalizeWhitespace(el.fullName || [el.firstName, el.lastName].filter(Boolean).join(' '));
    if (!name) {
      throw new CommandExecutionError('Sales Navigator lead row missing name');
    }
    const entityUrn = normalizeWhitespace(el.entityUrn || '');
    if (!profileUrlFromEntityUrn(entityUrn)) {
      throw new CommandExecutionError('Sales Navigator lead row missing profile identity');
    }
    leads.push({
      name,
      title: normalizeWhitespace(pos.title || ''),
      company: normalizeWhitespace(pos.companyName || ''),
      location: normalizeWhitespace(el.geoRegion || ''),
      degree: normalizeWhitespace(el.degree || ''),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the search after confirming a signed-in Sales Navigator session; transient bad payloads often clear on retry.
  2. Check the raw response shape from https://www.linkedin.com/sales-api/salesApiLeadSearch in the browser devtools and compare elements[] against the code's expectations.
  3. Update LEAD_SEARCH_DECORATION (line 11) from a live /sales/search/people request if LinkedIn bumped the decoration version.
  4. Update the library/CLI to the latest version in case upstream already handles the new schema.
  5. Report/persist the raw payload for debugging before failing, e.g. log JSON.stringify(json.elements) in a patched copy.

Example fix

// before
const pageLeads = parseLeads(json);
// after
const pageLeads = parseLeads(json).filter(Boolean); // plus upstream: skip non-object rows instead of throwing
// or defensively in caller:
try {
  const pageLeads = parseLeads(json);
} catch (e) {
  console.error('malformed lead row; payload:', JSON.stringify(json).slice(0, 500));
  throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

function hasValidLeadRows(json) {
  return !!json && typeof json === 'object' && Array.isArray(json.elements)
    && json.elements.every((el) => el && typeof el === 'object');
}

Type guard

function isLeadRow(el) {
  return typeof el === 'object' && el !== null && 'entityUrn' in el;
}

Try / catch

try {
  const pageLeads = parseLeads(json);
} catch (e) {
  if (e.message.includes('malformed lead row')) {
    // log raw payload for debugging, fall back to skipping this page
    console.error('Bad lead row; payload:', JSON.stringify(json).slice(0, 500));
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: The cli 'salesnav-search' command runs, requireLeadSearchResult returns the parsed JSON, and at least one element of json.elements is null, undefined, or a non-object (string/number) while iterating in parseLeads.

Common situations: LinkedIn redeployed Sales Navigator and changed the decorationId (com.linkedin.sales.deco.desktop.searchv2.LeadSearchResult-14), causing mixed/unexpected payload shapes; the response includes null placeholders in elements; LinkedIn A/B testing a new search result schema; an authenticated-but-degraded session returning partial payloads.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/018bda6b7159be2e. Report an issue: GitHub.