{"record":{"id":"91dee1c91fc1f4bd","repo":"santifer/career-ops","slug":"mokahr-careers-url-must-be-an-allowed-https-app-m","errorCode":null,"errorMessage":"mokahr: careers_url must be an allowed HTTPS app.mokahr.com tenant URL with a positive site ID","messagePattern":"mokahr: careers_url must be an allowed HTTPS app\\.mokahr\\.com tenant URL with a positive site ID","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/mokahr.mjs","lineNumber":212,"sourceCode":"  }\n  return jobs;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'mokahr',\n\n  detect(entry) {\n    const url = entry.careers_url;\n    if (typeof url !== 'string') return null;\n    if (!parseTenantUrl(url)) return null;\n    return { url };\n  },\n\n  async fetch(entry, ctx) {\n    const tenant = parseTenantUrl(entry.careers_url);\n    if (!tenant) {\n      throw new Error('mokahr: careers_url must be an allowed HTTPS app.mokahr.com tenant URL with a positive site ID');\n    }\n\n    const keywords = Array.isArray(entry.keywords) && entry.keywords.length\n      ? entry.keywords\n      : DEFAULT_KEYWORDS;\n    const entryMaxPages = Number(entry.max_pages) > 0 ? Number(entry.max_pages) : DEFAULT_MAX_PAGES;\n    const maxPages = Math.min(entryMaxPages, Number(ctx?.maxPages) > 0 ? Number(ctx.maxPages) : Infinity);\n\n    /** @type {Map<string, import('./_types.js').Job>} */\n    const seen = new Map();\n    const sleep = (ms) => (typeof ctx?.sleep === 'function' ? ctx.sleep(ms) : new Promise((r) => setTimeout(r, ms)));\n    let firstRequest = true;\n    let succeededOnce = false;\n\n    for (const keyword of keywords) {\n      for (let page = 1; page <= maxPages; page++) {\n        if (firstRequest) firstRequest = false;\n        else await sleep(INTER_PAGE_DELAY_MS);","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/mokahr.mjs#L194-L230","documentation":"The MokaHR provider's fetch() only accepts careers_url values that parse as an HTTPS URL on the app.mokahr.com host with a positive numeric site ID; parseTenantUrl() returns null otherwise and fetch() throws this error. It enforces both the host pin and the tenant-ID extraction contract before any request is made.","triggerScenarios":"entry.careers_url is missing/empty, uses http://, points at a non-app.mokahr.com host, lacks a site ID, or has a site ID that is zero/negative/non-numeric — e.g. careers_url: 'mokahr.com/mycompany' or 'https://app.mokahr.com' with no ID segment.","commonSituations":"A hand-edited portals.yml entry missing the tenant path; copying a frontend URL whose ID lives in a query/fragment the parser doesn't read; a company that uses MokaHR's other regional domains; an empty careers_url after a config migration.","solutions":["Set careers_url to the full HTTPS tenant URL on app.mokahr.com including its positive numeric site ID, e.g. https://app.mokahr.com/api/outer/9999 or the exact form parseTenantUrl expects (check providers/mokahr.mjs).","Open the company's MokaHR careers page and copy the site ID from the URL/network tab rather than guessing it.","If the tenant lives on a different MokaHR domain, that host is unsupported by this provider — request support for it rather than editing the check ad hoc.","Validate the URL by running parseTenantUrl(careers_url) in a scratch node -e script and confirming it returns an object."],"exampleFix":"// before\ncareers_url: https://app.mokahr.com\n// after\ncareers_url: https://app.mokahr.com/api/outer/12345","handlingStrategy":"validation","validationCode":"const u = new URL(entry.careers_url);\nconst ok = u.protocol === 'https:' && u.hostname === 'app.mokahr.com' && /\\d+/.test(u.pathname) && Number(u.pathname.match(/\\d+/)[0]) > 0;\nif (!ok) throw new Error(`${entry.name}: careers_url must be an app.mokahr.com tenant URL with a positive site ID`);","typeGuard":"const isMokaTenantUrl = (v) => { try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'app.mokahr.com' && Number(u.pathname.match(/\\d+/)?.[0]) > 0; } catch { return false; } };","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).startsWith('mokahr: careers_url must be')) {\n    console.error(`Fix careers_url for this entry: ${entry.careers_url}`);\n    return;\n  }\n  throw err;\n}","preventionTips":["Copy tenant URLs directly from the company's live MokaHR careers page including the numeric site ID.","Validate all careers_url values at config load (verify-pipeline.mjs) instead of mid-scan.","Don't strip query/path segments when copying the URL — the site ID lives there.","Document the expected URL form next to the entry in portals.yml."],"tags":["url","config","validation","mokahr"],"backgroundTag":"invalid-url","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}