{"record":{"id":"1b4b9935e5c20d52","repo":"jackwener/OpenCLI","slug":"job-url-must-be-a-https-www-linkedin-com-jobs-vi","errorCode":null,"errorMessage":"job-url must be a https://www.linkedin.com/jobs/view/<id> URL","messagePattern":"job-url must be a https://www\\.linkedin\\.com/jobs/view/<id> URL","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/job-detail.js","lineNumber":15,"sourceCode":"import { cli, Strategy } from '@jackwener/opencli/registry';\nimport { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';\nimport {\n  assertLinkedInAuthenticated,\n  assertSafeLinkedinUrl,\n  normalizeHttpUrl,\n  normalizeWhitespace,\n  unwrapEvaluateResult,\n} from './shared.js';\n\nfunction normalizeJobUrl(value) {\n  const url = assertSafeLinkedinUrl(value, 'job-url');\n  const parsed = new URL(url);\n  const match = parsed.pathname.match(/^\\/jobs\\/view\\/(\\d+)/) || parsed.search.match(/[?&]currentJobId=(\\d+)/);\n  if (!match) throw new ArgumentError('job-url must be a https://www.linkedin.com/jobs/view/<id> URL');\n  return `https://www.linkedin.com/jobs/search/?currentJobId=${match[1]}`;\n}\n\nfunction decodeLinkedinRedirect(url) {\n  if (!url) return '';\n  try {\n    const parsed = new URL(url);\n    if (parsed.pathname === '/redir/redirect/') return normalizeHttpUrl(parsed.searchParams.get('url') || '');\n  } catch {}\n  return normalizeHttpUrl(url);\n}\n\nfunction buildExtractionScript() {\n  return String.raw`(() => {\n    const clean = (s) => String(s || '').replace(/[\\u00a0\\u202f]+/g, ' ').replace(/\\s+/g, ' ').trim();\n    const readRenderedDescription = () => {\n      const expanders = Array.from(document.querySelectorAll('button, a'))\n        .filter((el) => /\\b(show more|see more|more)\\b/i.test(clean(el.innerText || el.textContent || el.getAttribute('aria-label') || '')));","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/job-detail.js#L1-L33","documentation":"normalizeJobUrl first runs assertSafeLinkedinUrl (rejecting non-LinkedIn/unsafe URLs) and then requires the path to match /jobs/view/<id> or a currentJobId=<id> query param. If neither pattern matches, an ArgumentError is thrown because no job id can be extracted to build the detail query URL.","triggerScenarios":"Calling the job-detail command with a job URL like https://www.linkedin.com/jobs/view/some-slug without a numeric id, a jobs collection URL (/jobs/search/...), a truncated URL, or a non-LinkedIn URL that slipped past the safety check but lacks the job-id pattern.","commonSituations":"Copying a job-search results URL instead of a specific job view URL, URLs where the id lives only in a tracking redirect, sharing mobile app links (linkd.in shorteners) that were never resolved, or malformed saved links from a database.","solutions":["Pass the full job view URL containing a numeric id: https://www.linkedin.com/jobs/view/<numericId>/.","If you have a search URL, extract currentJobId from it or open the job and copy the canonical /jobs/view/<id> URL.","Resolve shortened (lnkd.in) or redirect URLs to their final linkedin.com/jobs/view/<id> form before calling.","Validate the id client-side: /linkedin\\.com\\/(jobs\\/view\\/(\\d+)|.*currentJobId=(\\d+))/ before invoking."],"exampleFix":"// before\njobDetail('https://www.linkedin.com/jobs/search/?keywords=engineer'); // ArgumentError\n\n// after\njobDetail('https://www.linkedin.com/jobs/view/3987654321/');","handlingStrategy":"validation","validationCode":"const JOB_URL_RE = /^https:\\/\\/www\\.linkedin\\.com\\/jobs\\/view\\/(\\d+)/;\nconst jobIdFromUrl = (u) => {\n  const m = String(u).match(JOB_URL_RE) || String(u).match(/[?&]currentJobId=(\\d+)/);\n  if (!m) throw new TypeError('Expected a linkedin.com/jobs/view/<id> URL');\n  return m[1];\n};","typeGuard":"const isLinkedinJobUrl = (u) =>\n  typeof u === 'string' &&\n  (/^https:\\/\\/www\\.linkedin\\.com\\/jobs\\/view\\/\\d+/.test(u) || /[?&]currentJobId=\\d+/.test(u));","tryCatchPattern":"try {\n  await linkedinJobDetail(jobUrl);\n} catch (e) {\n  if (e instanceof ArgumentError && /job-url/.test(e.message)) {\n    const id = await resolveJobIdFromSearchOrRedirect(jobUrl);\n    return linkedinJobDetail(`https://www.linkedin.com/jobs/view/${id}/`);\n  }\n  throw e;\n}","preventionTips":["Store canonical /jobs/view/<numericId> URLs, not search-result or shortener links.","Resolve lnkd.in and tracking redirects to final URLs before calling.","Sanitize user-supplied job links with a regex whitelist at your API boundary.","Extract ids from currentJobId query params when starting from a jobs search page."],"tags":["cli","argument-validation","url","linkedin"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}