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
- 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).
- Decode the three segments and verify none are empty, 'undefined', 'null', or 'not_available' before passing the URL.
- If only the profileId is known, resolve the full lead URL via salesnav-search or fall back to passing the person's exact name.
- 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
- Take lead_url values from live salesnav-search output, not cached pages or scraped previews.
- Decode and check the three segments for 'undefined'/'not_available' whenever URLs pass through templates or logs.
- Fall back to the participant's exact name when profile identity fields are unresolved.
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
- job-url must be a https://www.linkedin.com/jobs/view/
- must be a LinkedIn URL
- must be an https LinkedIn URL without credentials or port
- LinkedIn services-read requires a /in/
- LinkedIn services-read requires a /services/page/
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)