{"record":{"id":"239909ec6f4c1434","repo":"jackwener/OpenCLI","slug":"linkedin-messengermessages-discovery-returned-an-u","errorCode":null,"errorMessage":"LinkedIn messengerMessages discovery returned an unsafe or mismatched URL.","messagePattern":"LinkedIn messengerMessages discovery returned an unsafe or mismatched URL\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/thread-snapshot.js","lineNumber":193,"sourceCode":"  if (!Array.isArray(apiUrls) || apiUrls.length === 0) {\n    throw new CommandExecutionError('LinkedIn did not issue a messengerMessages API request for this thread.');\n  }\n  for (const value of apiUrls) {\n    let url;\n    try {\n      url = new URL(value);\n    } catch {\n      throw new CommandExecutionError('LinkedIn messengerMessages discovery returned an invalid URL.');\n    }\n    let decoded = value;\n    try { decoded = decodeURIComponent(value); } catch {}\n    if (url.protocol !== 'https:'\n      || url.hostname !== LINKEDIN_DOMAIN\n      || url.pathname !== '/voyager/api/voyagerMessagingGraphQL/graphql'\n      || !/^messengerMessages\\.[a-f0-9]+$/i.test(url.searchParams.get('queryId') || '')\n      || !threadId\n      || !decoded.includes(threadId)) {\n      throw new CommandExecutionError('LinkedIn messengerMessages discovery returned an unsafe or mismatched URL.');\n    }\n  }\n  return apiUrls;\n}\n\nfunction parseThreadPages(pages) {\n  if (!Array.isArray(pages) || pages.length === 0) {\n    throw new CommandExecutionError('LinkedIn messengerMessages API returned no pages.');\n  }\n\n  const entities = new Map();\n  const apiUrls = [];\n  for (const page of pages) {\n    if (!page || typeof page !== 'object' || Array.isArray(page) || typeof page.url !== 'string') {\n      throw new CommandExecutionError('LinkedIn messengerMessages API returned a malformed page wrapper.');\n    }\n    const normalized = page.json;\n    if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/thread-snapshot.js#L175-L211","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: generic messaging URL (threadId extraction fails)\nconst url = 'https://www.linkedin.com/messaging/?filter=unread';\n// after: exact thread URL\nconst url = 'https://www.linkedin.com/messaging/thread/2-AbCdEf/';","handlingStrategy":"validation","validationCode":"function isSafeMessengerUrl(value, threadId) {\n  try {\n    const u = new URL(value);\n    const decoded = decodeURIComponent(value);\n    return u.protocol === 'https:'\n      && u.hostname === 'www.linkedin.com'\n      && u.pathname === '/voyager/api/voyagerMessagingGraphQL/graphql'\n      && /^messengerMessages\\.[a-f0-9]+$/i.test(u.searchParams.get('queryId') || '')\n      && Boolean(threadId) && decoded.includes(threadId);\n  } catch { return false; }\n}","typeGuard":"function isHttpsLinkedInUrl(v) {\n  try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'www.linkedin.com'; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  const snapshot = await cli.linkedin.threadSnapshot({ url: threadUrl });\n} catch (err) {\n  if (err.message.includes('unsafe or mismatched URL')) {\n    // verify the URL is an exact /messaging/thread/<id>/ link, reload, retry\n  } else throw err;\n}","preventionTips":["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."],"tags":["linkedin","url-validation","security","scraping"],"backgroundTag":"url-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}