{"record":{"id":"6d3ee3415bd658a9","repo":"jackwener/OpenCLI","slug":"label-must-be-an-exact-https-www-linkedin-com-6d3ee3","errorCode":null,"errorMessage":"${label} must be an exact https://www.linkedin.com/messaging/thread/<id>/ URL","messagePattern":"(.+?) must be an exact https://www\\.linkedin\\.com/messaging/thread/<id>/ URL","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/thread-snapshot.js","lineNumber":20,"sourceCode":"import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';\nimport {\n  canonicalizeLinkedInThreadUrl,\n  normalizeWhitespace,\n  requireLinkedInCookie,\n  unwrapEvaluateResult,\n} from './shared.js';\n\nconst LINKEDIN_DOMAIN = 'www.linkedin.com';\n\nfunction requireStringArg(args, key, label = key) {\n  const value = normalizeWhitespace(args[key]);\n  if (!value) throw new ArgumentError(`${label} is required`);\n  return value;\n}\n\nfunction requireLinkedInThreadUrl(value, label) {\n  const url = canonicalizeLinkedInThreadUrl(value);\n  if (!url) throw new ArgumentError(`${label} must be an exact https://www.linkedin.com/messaging/thread/<id>/ URL`);\n  return url;\n}\n\nfunction parseMaxScrolls(value) {\n  if (value === undefined || value === null || value === '') return 30;\n  const scrolls = Number(value);\n  if (!Number.isInteger(scrolls) || scrolls < 0 || scrolls > 80) {\n    throw new ArgumentError('--max-scrolls must be an integer between 0 and 80');\n  }\n  return scrolls;\n}\n\nfunction buildThreadApiDiscoveryScript(maxScrolls) {\n  return String.raw`(async () => {\n    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n    const pageText = document.body ? (document.body.innerText || '') : '';\n    const authRequired = /\\b(sign in|log in|join linkedin)\\b/i.test(pageText)\n      || /linkedin\\.com\\/(login|checkpoint|authwall|uas)/i.test(location.href)","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/thread-snapshot.js#L2-L38","documentation":"requireLinkedInThreadUrl canonicalizes the input via canonicalizeLinkedInThreadUrl and throws ArgumentError when the value does not canonicalize to an exact https://www.linkedin.com/messaging/thread/<id>/ URL. The URL must match LinkedIn's messaging thread pattern precisely — other paths, hosts, or extra fragments fail.","triggerScenarios":"Passing a profile or feed URL instead of a messaging thread URL, a thread URL without the ID, a URL with extra path segments or query strings that fail canonicalization, a non-www host, or http instead of https.","commonSituations":"Copying the wrong URL from the browser (post/feed instead of thread), URL-encoding mangling the ID, using a mobile or short-link form, truncated thread ID from logs.","solutions":["Copy the full URL while viewing the thread: https://www.linkedin.com/messaging/thread/<threadId>/.","Remove query strings/extra fragments; ensure https and the www.linkedin.com host.","Fix truncation/encoding of the thread ID (long alphanumeric string).","Verify the shape before calling with a regex like /^https:\\/\\/www\\.linkedin\\.com\\/messaging\\/thread\\/[A-Za-z0-9-_]+\\/$/."],"exampleFix":"// before\n--thread-url \"https://www.linkedin.com/feed/update/urn:li:activity:123/\"\n// after\n--thread-url \"https://www.linkedin.com/messaging/thread/AbCdEf123_/\"","handlingStrategy":"validation","validationCode":"const THREAD_URL_RE = /^https:\\/\\/www\\.linkedin\\.com\\/messaging\\/thread\\/[A-Za-z0-9_-]+\\/$/;\nfunction isValidThreadUrl(u) {\n  return typeof u === 'string' && THREAD_URL_RE.test(u.trim());\n}\nif (!isValidThreadUrl(threadUrl)) throw new Error('Pass an exact https://www.linkedin.com/messaging/thread/<id>/ URL');","typeGuard":"function isLinkedInThreadUrl(v) {\n  if (typeof v !== 'string') return false;\n  try {\n    const u = new URL(v);\n    return u.protocol === 'https:' && u.hostname === 'www.linkedin.com' &&\n      /^\\/messaging\\/thread\\/[A-Za-z0-9_-]+\\/$/.test(u.pathname);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  await threadSnapshot({ 'thread-url': threadUrl });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('messaging/thread')) {\n    console.error('Not a valid thread URL. Copy it from an open LinkedIn message thread.');\n  } else throw e;\n}","preventionTips":["Copy the URL directly from an open messaging thread; it must contain /messaging/thread/<id>/.","Strip query strings and ensure the www host with https.","Check the thread ID is not truncated or URL-mangled by your tooling.","Pre-validate with the regex above before invoking the command."],"tags":["argument-validation","url-validation","linkedin","messaging"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}