jackwener/OpenCLI · error · ArgumentError

Sales Navigator lead URL must contain resolved profileId, au

Error message

Sales Navigator lead URL must contain resolved profileId, authType, and authToken

What it means

An ArgumentError from parseThreadInput (clis/linkedin/salesnav-thread.js:51) raised when a https://www.linkedin.com/sales/lead/... URL matches the lead path but its three path segments do not form a valid salesProfile URN. parseSalesProfileUrn rejects segments that are empty, 'undefined', 'null', or 'not_available', which typically means the lead URL was captured from a page where profile identity fields were unresolved. The library throws rather than silently treating the URL as a name.

Source

Thrown at clis/linkedin/salesnav-thread.js:51

function parseThreadInput(value) {
  const raw = normalizeWhitespace(value);
  if (!raw) return ['empty', ''];
  if (/^2-[A-Za-z0-9+/=_-]+$/.test(raw)) return ['thread_id', raw];
  if (/^urn:li:fs_salesProfile:\(/.test(raw)) {
    const urn = parseSalesProfileUrn(raw);
    if (!urn) throw new ArgumentError('Sales Navigator recipient urn must be urn:li:fs_salesProfile:(profileId,authType,authToken)');
    return ['recipient_urn', urn];
  }
  try {
    const url = new URL(raw);
    if (url.protocol !== 'https:' || url.username || url.password || url.port || !isLinkedInHost(url.hostname)) return ['name', raw.toLowerCase()];
    const inboxMatch = url.pathname.match(/^\/sales\/inbox\/([^/]+)\/?$/i);
    if (inboxMatch) return ['thread_id', decodeURIComponent(inboxMatch[1])];
    const leadMatch = url.pathname.match(/^\/sales\/lead\/([^,/]+),([^,/]+),([^/]+)\/?$/i);
    if (leadMatch) {
      const urn = `urn:li:fs_salesProfile:(${decodeURIComponent(leadMatch[1])},${decodeURIComponent(leadMatch[2])},${decodeURIComponent(leadMatch[3])})`;
      if (!parseSalesProfileUrn(urn)) {
        throw new ArgumentError('Sales Navigator lead URL must contain resolved profileId, authType, and authToken');
      }
      return ['recipient_urn', urn];
    }
  } catch (err) {
    if (err instanceof ArgumentError) throw err;
    // Fall through to name matching for non-URL text.
  }
  return ['name', raw.toLowerCase()];
}

function salesnavThreadUrl(threadId) {
  return threadId ? `https://www.linkedin.com/sales/inbox/${encodeURIComponent(threadId)}` : '';
}

function threadApiUrl(threadId, messageCount) {
  return `${THREADS_BASE}/${encodeURIComponent(threadId)}?decoration=${encodeRestliDecoration(THREAD_DECORATION)}&count=1&messageCount=${messageCount}`;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open the lead URL in a browser; if it 404s or shows an unresolved profile, get a fresh URL from a live salesnav-search run (lead_url column).
  2. Decode the three segments and verify none are empty, 'undefined', 'null', or 'not_available' before passing the URL.
  3. If only the profileId is known, resolve the full lead URL via salesnav-search or fall back to passing the person's exact name.
  4. Re-copy the URL from the live Sales Navigator lead page rather than from logs or cached search results.

Example fix

// before
await run('linkedin salesnav-thread', ['https://www.linkedin.com/sales/lead/ACoAABC,undefined,AbCd/']);
// after (validate segments first)
const m = url.pathname.match(/^\/sales\/lead\/([^,/]+),([^,/]+),([^/]+)\/?$/);
const segs = m.slice(1).map(decodeURIComponent);
if (segs.some((s) => !s || s === 'undefined' || s === 'not_available')) {
  throw new Error('unresolved lead URL segments');
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate lead URL segments before passing them:
function leadUrlSegmentsAreResolved(url) {
  const m = url.match(/^https:\/\/www\.linkedin\.com\/sales\/lead\/([^,/]+),([^,/]+),([^/]+)\/?$/);
  if (!m) return false;
  return m.slice(1).every((s) => {
    const v = decodeURIComponent(s);
    return v && !['undefined', 'null', 'not_available'].includes(v);
  });
}
if (!leadUrlSegmentsAreResolved(leadUrl)) throw new Error('lead URL has unresolved profileId/authType/authToken');

Type guard

function isResolvedLeadUrl(value) {
  if (typeof value !== 'string') return false;
  const m = value.match(/linkedin\.com\/sales\/lead\/([^,/]+),([^,/]+),([^/]+)\/?$/);
  if (!m) return false;
  return m.slice(1).map(decodeURIComponent).every(
    (s) => s && !['undefined', 'null', 'not_available'].includes(s)
  );
}

Try / catch

try {
  await run('linkedin salesnav-thread', [leadUrl]);
} catch (err) {
  if (err instanceof ArgumentError && /lead URL/.test(err.message)) {
    console.error('Lead URL unresolved; re-fetch via salesnav-search lead_url column.');
    return run('linkedin salesnav-thread', [lead.name]);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a lead URL like https://www.linkedin.com/sales/lead/ACoAABC,not_available,XYZ/ scraped from an unresolved profile card; a URL whose segments were percent-encoded objects ('undefined') from string interpolation; malformed URLs with missing third segment matching only via regex leniency.

Common situations: Scraping lead URLs from cached/preview pages where authToken was not yet resolved; templating URLs from data objects with undefined fields; copying lead URLs from older Sales Navigator exports with a different segment layout.

Related errors


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