jackwener/OpenCLI · error · CommandExecutionError
LinkedIn messengerMessages discovery returned an unsafe or m
Error message
LinkedIn messengerMessages discovery returned an unsafe or mismatched URL.
What it means
Thrown by validateThreadApiUrls when a discovered URL parses but fails the safety/mismatch checks: it must be https, on www.linkedin.com, use the exact voyager GraphQL path, carry a messengerMessages.<hex> queryId, and decode to include the current threadId. This guard prevents replaying requests that belong to another thread, domain, or an unexpected endpoint.
Source
Thrown at clis/linkedin/thread-snapshot.js:193
if (!Array.isArray(apiUrls) || apiUrls.length === 0) {
throw new CommandExecutionError('LinkedIn did not issue a messengerMessages API request for this thread.');
}
for (const value of apiUrls) {
let url;
try {
url = new URL(value);
} catch {
throw new CommandExecutionError('LinkedIn messengerMessages discovery returned an invalid URL.');
}
let decoded = value;
try { decoded = decodeURIComponent(value); } catch {}
if (url.protocol !== 'https:'
|| url.hostname !== LINKEDIN_DOMAIN
|| url.pathname !== '/voyager/api/voyagerMessagingGraphQL/graphql'
|| !/^messengerMessages\.[a-f0-9]+$/i.test(url.searchParams.get('queryId') || '')
|| !threadId
|| !decoded.includes(threadId)) {
throw new CommandExecutionError('LinkedIn messengerMessages discovery returned an unsafe or mismatched URL.');
}
}
return apiUrls;
}
function parseThreadPages(pages) {
if (!Array.isArray(pages) || pages.length === 0) {
throw new CommandExecutionError('LinkedIn messengerMessages API returned no pages.');
}
const entities = new Map();
const apiUrls = [];
for (const page of pages) {
if (!page || typeof page !== 'object' || Array.isArray(page) || typeof page.url !== 'string') {
throw new CommandExecutionError('LinkedIn messengerMessages API returned a malformed page wrapper.');
}
const normalized = page.json;
if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {View on GitHub (pinned to 49907e53dc)
Solutions
- Confirm the --url argument is an exact https://www.linkedin.com/messaging/thread/<id>/ URL so the threadId regex extracts a non-empty id.
- Reload the page before running so stale resource entries from other threads are cleared.
- If LinkedIn changed the endpoint or queryId format, update LINKEDIN_DOMAIN, the pathname check, and the queryId regex to match the new API surface.
Example fix
// before: generic messaging URL (threadId extraction fails) const url = 'https://www.linkedin.com/messaging/?filter=unread'; // after: exact thread URL const url = 'https://www.linkedin.com/messaging/thread/2-AbCdEf/';
Defensive patterns
Strategy: validation
Validate before calling
function isSafeMessengerUrl(value, threadId) {
try {
const u = new URL(value);
const decoded = decodeURIComponent(value);
return u.protocol === 'https:'
&& u.hostname === 'www.linkedin.com'
&& u.pathname === '/voyager/api/voyagerMessagingGraphQL/graphql'
&& /^messengerMessages\.[a-f0-9]+$/i.test(u.searchParams.get('queryId') || '')
&& Boolean(threadId) && decoded.includes(threadId);
} catch { return false; }
} Type guard
function isHttpsLinkedInUrl(v) {
try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'www.linkedin.com'; }
catch { return false; }
} Try / catch
try {
const snapshot = await cli.linkedin.threadSnapshot({ url: threadUrl });
} catch (err) {
if (err.message.includes('unsafe or mismatched URL')) {
// verify the URL is an exact /messaging/thread/<id>/ link, reload, retry
} else throw err;
} Prevention
- Only pass exact https://www.linkedin.com/messaging/thread/<id>/ URLs.
- Reload the thread page before running to avoid stale entries from other conversations.
- Pin/monitor LinkedIn API changes; update endpoint and queryId checks when LinkedIn ships a new path.
When it happens
Trigger: Any of: url.protocol !== 'https:', hostname !== 'www.linkedin.com', pathname !== '/voyager/api/voyagerMessagingGraphQL/graphql', missing/malformed queryId, empty threadId from the thread URL, or the decoded URL not containing the threadId.
Common situations: Developer passed a messaging URL whose path doesn't match /messaging/thread/<id>/ (so threadId is empty); LinkedIn A/B-tested a new endpoint path; requests captured from a different conversation remained in the performance buffer; a proxied/dev environment rewrote the hostname.
Related errors
- LinkedIn messengerMessages discovery returned an invalid URL
- google images returned a result row without stable external
- HLTV parser returned an off-domain URL: ${url.toString()}
- LinkedIn company URL must point to linkedin.com
- LinkedIn company URL must look like /company/<name>
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/239909ec6f4c1434.
Report an issue: GitHub.