{"record":{"id":"29abd62e0f13458d","repo":"jackwener/OpenCLI","slug":"linkedin-job-detail-returned-malformed-extraction","errorCode":null,"errorMessage":"LinkedIn job detail returned malformed extraction payload","messagePattern":"LinkedIn job detail returned malformed extraction payload","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/job-detail.js","lineNumber":121,"sourceCode":"    return {\n      url: location.href,\n      title: h1,\n      company,\n      company_url,\n      location: locationLine,\n      workplace_type: workplaceMatch ? workplaceMatch[1] : '',\n      job_type: jobTypeMatch ? jobTypeMatch[1] : '',\n      applicants: applicantsMatch ? applicantsMatch[1] : '',\n      listed: listedMatch ? listedMatch[1] : '',\n      apply_url,\n      description,\n    };\n  })()`;\n}\n\nfunction normalizeDetail(row) {\n  if (!row || typeof row !== 'object') {\n    throw new CommandExecutionError('LinkedIn job detail returned malformed extraction payload');\n  }\n  const title = normalizeWhitespace(row.title);\n  if (!title) throw new CommandExecutionError('LinkedIn job detail could not find a job title');\n  return {\n    title,\n    company: normalizeWhitespace(row.company),\n    location: normalizeWhitespace(row.location),\n    workplace_type: normalizeWhitespace(row.workplace_type),\n    job_type: normalizeWhitespace(row.job_type),\n    applicants: normalizeWhitespace(row.applicants),\n    listed: normalizeWhitespace(row.listed),\n    apply_url: decodeLinkedinRedirect(normalizeWhitespace(row.apply_url)),\n    company_url: normalizeHttpUrl(row.company_url),\n    url: normalizeHttpUrl(row.url),\n    description: normalizeWhitespace(row.description),\n  };\n}\n","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/job-detail.js#L103-L139","documentation":"normalizeDetail validates the raw row produced by the in-page extraction script before mapping it to the output shape. If the evaluated script returned null, undefined, or a non-object (extraction failed entirely), a CommandExecutionError is thrown describing the payload as malformed. This keeps downstream field normalization from crashing on a missing object.","triggerScenarios":"During `linkedin job-detail`, the injected extraction script returns no object — the job page didn't render its detail section, a selector missed, the page showed an error/login wall, or evaluate returned an unwrapped null.","commonSituations":"Job posting removed/expired so the page renders a 'no longer available' state, page still loading when extraction ran (timing), LinkedIn DOM restructure changing selectors, or a login wall replacing the detail content.","solutions":["Re-run the command; transient timing issues where the page hadn't finished rendering are the most common cause.","Open the job URL in the browser to confirm the posting still exists and renders a detail view.","Verify the session can view the job (some jobs require sign-in or region access).","If the page renders fine manually but the error persists, the extraction selectors are stale — update/report the library."],"exampleFix":"// before\nconst d = jobDetail(url); // CommandExecutionError: malformed payload (page slow)\n\n// after\nawait page.goto(url); await waitForSelector('.job-details');\nconst d = jobDetail(url);","handlingStrategy":"retry","validationCode":"// before invoking, ensure the job page actually rendered a detail section\nawait page.goto(jobUrl);\nawait page.waitForSelector('[class*=\"job-details\"], .jobs-details', { timeout: 15000 });","typeGuard":"const isMalformedPayloadError = (e) => e instanceof CommandExecutionError && /malformed extraction payload/i.test(e?.message || '');","tryCatchPattern":"try {\n  return await linkedinJobDetail(jobUrl);\n} catch (e) {\n  if (isMalformedPayloadError(e)) {\n    await sleep(3000);\n    return retry(linkedinJobDetail, { args: [jobUrl], attempts: 2 });\n  }\n  throw e;\n}","preventionTips":["Confirm the posting is live (not expired/removed) before extraction.","Allow the page to finish hydrating before running extraction scripts.","Handle 'no longer available' job pages as an expected empty case in your code.","Track LinkedIn DOM changes; refresh extraction selectors when failures spike."],"tags":["scraping","extraction","linkedin","malformed-data"],"backgroundTag":"malformed-response-payload","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}