{"record":{"id":"8dd31ba274c30c28","repo":"santifer/career-ops","slug":"remotli-invalid-url-url","errorCode":null,"errorMessage":"remotli: invalid URL: ${url}","messagePattern":"remotli: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/remotli.mjs","lineNumber":238,"sourceCode":"  const description = htmlToText(job.description);\n  if (description) out.description = description;\n\n  const postedAt = toEpochMs(job.publishedAt || job.createdAt);\n  if (postedAt !== undefined) out.postedAt = postedAt;\n\n  const salary = resolveSalary(job);\n  if (salary) out.salary = salary;\n\n  return out;\n}\n\n/** Guard the API URL: HTTPS + remotli.ch only. */\nfunction assertRemotliUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`remotli: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`remotli: URL must use HTTPS: ${url}`);\n  if (!HOST_RE.test(parsed.hostname))\n    throw new Error(`remotli: untrusted hostname \"${parsed.hostname}\" — must be remotli.ch`);\n  return url;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'remotli',\n\n  detect(entry) {\n    const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';\n    if (!raw) return null;\n    let parsed;\n    try {\n      parsed = new URL(raw);\n    } catch {","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/remotli.mjs#L220-L256","documentation":"assertRemotliUrl throws this when new URL(url) raises — the string is not a parseable absolute URL. This is the structural guard before the HTTPS and hostname checks. remotli builds paginated API URLs internally, so this typically means the constructed URL string is malformed.","triggerScenarios":"The URL passed to assertRemotliUrl has no protocol, contains illegal characters, or is otherwise rejected by the URL constructor. Since remotli constructs URLs from ORIGIN + API_PATH constants, a corrupt ORIGIN constant or a bad page/limit interpolation would trigger it.","commonSituations":"The ORIGIN or API_PATH constant was edited incorrectly (missing scheme, typo); template-literal interpolation produced an empty or malformed segment; a code change broke the URL-building logic.","solutions":["Inspect the exact URL string passed to assertRemotliUrl — log it before the call.","Verify ORIGIN starts with 'https://' and API_PATH begins with '/'.","Check that page/limit values used in the template literal are valid numbers, not NaN or undefined.","Revert recent changes to the URL-building constants."],"exampleFix":"// before — ORIGIN missing scheme\nconst ORIGIN = 'remotli.ch';\n// after\nconst ORIGIN = 'https://remotli.ch';","handlingStrategy":"validation","validationCode":"function isParseableUrl(s) {\n  try { new URL(s); return true; } catch { return false; }\n}\n// remotli builds URLs from constants — validate the constants at startup\nif (!isParseableUrl(`${ORIGIN}${API_PATH}`)) {\n  throw new Error('remotli: ORIGIN/API_PATH misconfigured — URL will not parse');\n}","typeGuard":"null","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (e) {\n  if (/remotli: invalid URL/.test(e.message)) {\n    console.error(`[bug] remotli URL construction broken: ${e.message}`);\n  } else throw e;\n}","preventionTips":["Since remotli builds URLs from internal constants, a parse failure is a code bug — unit-test the URL builder.","Keep ORIGIN set to 'https://remotli.ch'.","Add a startup assertion that the constructed base URL parses."],"tags":["url-validation","provider","remotli","malformed-url","internal-bug"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}