{"record":{"id":"5722c6a123e4552b","repo":"santifer/career-ops","slug":"teamtailor-invalid-url-url","errorCode":null,"errorMessage":"teamtailor: invalid URL: ${url}","messagePattern":"teamtailor: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/teamtailor.mjs","lineNumber":38,"sourceCode":"// explicit `provider: teamtailor`. Either way the fetch is HTTPS-only with\n// `redirect: 'error'`. Job `<link>`s (branded domains) are emitted as-is and\n// never fetched.\n\nconst TEAMTAILOR_HOST_RE = /^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\\.teamtailor\\.com$/i;\n\n/**\n * Validate a feed URL before fetching. Always HTTPS-only. The hostname is\n * pinned to `*.teamtailor.com` for auto-detected entries; an explicit\n * `provider: teamtailor` entry may use its configured branded host.\n * @param {string} url\n * @param {{ explicit?: boolean }} [opts]\n */\nfunction assertFeedUrl(url, { explicit = false } = {}) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`teamtailor: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`teamtailor: URL must use HTTPS: ${url}`);\n  if (!explicit && !TEAMTAILOR_HOST_RE.test(parsed.hostname)) {\n    throw new Error(`teamtailor: untrusted hostname \"${parsed.hostname}\" — must be <slug>.teamtailor.com (or set \"provider: teamtailor\" to use a branded careers domain)`);\n  }\n  return url;\n}\n\n// Derive the RSS feed URL from a tracked_companies entry by normalizing any\n// path on the configured host to /jobs.rss. Auto-detection (explicit=false)\n// only claims *.teamtailor.com hosts; an explicit `provider: teamtailor` entry\n// (explicit=true) may use a branded careers host. Returns null otherwise.\n/**\n * @param {import('./_types.js').PortalEntry} entry\n * @param {{ explicit?: boolean }} [opts]\n */\nfunction resolveFeedUrl(entry, { explicit = false } = {}) {\n  const raw = entry?.api || entry?.careers_url || '';","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/teamtailor.mjs#L20-L56","documentation":"assertFeedUrl wraps new URL in try/catch and throws 'teamtailor: invalid URL' when the constructor rejects the string. This is the first of three sequential checks (parse → https → host); it fires before host validation, so the value is not even syntactically valid. The check runs on the derived /jobs.rss URL as well as any explicit feed URL.","triggerScenarios":"The feed URL string cannot be parsed: missing scheme, embedded spaces, or a corrupted value. For auto-detected entries this typically means careers_url was malformed; for explicit provider: teamtailor entries it can be a malformed api/careers_url.","commonSituations":"YAML typo, a paste that dropped 'https://', or an unquoted scalar that YAML parsed into multiple tokens.","solutions":["Prefix the value with https://","Quote the scalar in portals.yml if it contains special characters","Use the full form https://<slug>.teamtailor.com (the provider appends /jobs.rss itself)"],"exampleFix":"# before\ncareers_url: acme.teamtailor.com/jobs\n# after\ncareers_url: https://acme.teamtailor.com","handlingStrategy":"validation","validationCode":"function isValidUrl(v) {\n  if (typeof v !== 'string' || !v) return false;\n  try { new URL(v); return true; } catch { return false; }\n}\nif (!isValidUrl(entry.api || entry.careers_url)) {\n  console.warn(`${entry.name}: teamtailor feed URL is not a valid absolute URL`);\n}","typeGuard":"/** @param {unknown} v @returns {v is string} */\nfunction isValidUrl(v) {\n  if (typeof v !== 'string' || !v) return false;\n  try { new URL(v); return true; } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Always include the https:// scheme in YAML.","Run a config-lint pass parsing every feed URL."],"tags":["url-validation","config","teamtailor","portals-yml"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}