jackwener/OpenCLI · error · CommandExecutionError

Sales Navigator lead row missing name

Error message

Sales Navigator lead row missing name

What it means

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.

Source

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

  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 || ''),
      profile_url: profileUrlFromEntityUrn(entityUrn),
      lead_url: leadUrlFromEntityUrn(entityUrn),
      recipient_urn: entityUrn,
    });
  }
  return leads;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the search; ghost/anonymous rows are often transient and tied to session state.
  2. Sign in to LinkedIn with Sales Navigator access in the automation browser so restricted profile data is served.
  3. Inspect the offending row in devtools (salesApiLeadSearch response) to see which name field changed or is absent.
  4. Patch parseLeads to skip nameless rows instead of throwing if partial results are acceptable to your workflow.
  5. Update the CLI/library in case upstream adapted to a renamed/added name field in the decoration schema.

Example fix

// before
if (!name) {
  throw new CommandExecutionError('Sales Navigator lead row missing name');
}
// after
if (!name) {
  console.warn('Skipping lead row without name:', el.entityUrn);
  continue; // skip instead of aborting the whole search
}
Defensive patterns

Strategy: validation

Validate before calling

function leadHasName(el) {
  const n = String(el?.fullName || [el?.firstName, el?.lastName].filter(Boolean).join(' ') || '').trim();
  return n.length > 0;
}
// pre-check a page: const usable = json.elements.filter(leadHasName);

Type guard

function hasName(el) {
  return typeof el === 'object' && el !== null
    && (typeof el.fullName === 'string' && el.fullName.trim() !== ''
      || (typeof el.firstName === 'string' && el.firstName.trim() !== ''));
}

Try / catch

try {
  const pageLeads = parseLeads(json);
} catch (e) {
  if (e.message.includes('missing name')) {
    console.warn('Nameless lead row skipped; possibly a restricted/anonymized profile');
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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