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

  1. Confirm the --url argument is an exact https://www.linkedin.com/messaging/thread/<id>/ URL so the threadId regex extracts a non-empty id.
  2. Reload the page before running so stale resource entries from other threads are cleared.
  3. 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

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


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