jackwener/OpenCLI · error · ArgumentError

Sales Navigator lead URL must contain resolved profileId…

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.

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.

Appendix: 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)