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
- 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/<id> UR
- --recipient must be a Sales Navigator lead URL, Sales Naviga
- Sales Navigator recipient urn must be urn:li:fs_salesProfile
- LinkedIn services-read requires a /in/<handle>/ profile URL
- LinkedIn services-read requires a /services/page/<id>/ URL
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/78626221ab8599df.
Report an issue: GitHub.