{"record":{"id":"70097fb5cb190531","repo":"santifer/career-ops","slug":"solidjobs-careers-url-required","errorCode":null,"errorMessage":"solidjobs: careers_url required","messagePattern":"solidjobs: careers_url required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/solidjobs.mjs","lineNumber":62,"sourceCode":"  detect(entry) {\n    const url = entry.careers_url || '';\n    try {\n      const parsed = new URL(url);\n      if (parsed.hostname === 'solid.jobs' && parsed.pathname.startsWith('/public-api/offers/'))\n        return { url };\n    } catch {}\n    return null;\n  },\n  \n  /**\n   * Fetches and normalizes job offers from the SolidJobs public API.\n   * * @param {{ careers_url?: string, name: string }} entry - The configuration entry being processed.\n   * @param {{ fetchJson: (url: string, opts?: { redirect?: 'error'|'follow'|'manual' }) => Promise<any> }} ctx - HTTP context.\n   * @returns {Promise<Array<{title: string, url: string, company: string, location: string}>>} Array of parsed job offers.\n   */\n  async fetch(entry, ctx) {\n    const url = entry.careers_url;\n    if (!url) throw new Error('solidjobs: careers_url required');\n    assertUrl(url);\n    // redirect:'error' prevents SSRF via server-side redirects\n    const json = await ctx.fetchJson(url, { redirect: 'error' });\n    if (!json || !Array.isArray(json.jobs)) {\n      throw new Error(`solidjobs: unexpected API response — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);\n    }\n\n    /** @type {Array<{ title?: string, url?: string, company?: string, locations?: string | string[] }>} */\n    const jobs = json.jobs;\n\n    return jobs\n      .filter(j => j && typeof j === 'object' && typeof j.url === 'string' && j.url.trim() !== '')\n      .map(j => ({\n        title: j.title || '',\n        url: /** @type {string} */ (j.url || '').trim(),\n        company: j.company || entry.name,\n        location: Array.isArray(j.locations) ? j.locations.join(', ') : (typeof j.locations === 'string' ? j.locations : ''),\n      }));","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/solidjobs.mjs#L44-L80","documentation":"The solidjobs provider's fetch() reads the board URL exclusively from entry.careers_url. Unlike providers that can derive an endpoint from a company name, this one has no fallback, so a config entry without careers_url is unusable and the provider fails fast with this message.","triggerScenarios":"A portals.yml entry claims solidjobs as its provider (or is auto-matched to it) but omits the careers_url field entirely, or sets it to an empty string/null.","commonSituations":"Adding a new company by copying an existing entry but deleting the URL; a YAML indentation error nesting careers_url under the wrong key so the entry object never sees it; a renamed field after a config schema change.","solutions":["Add careers_url: 'https://solid.jobs/public-api/offers/...' to the entry in portals.yml","Run node doctor.mjs or verify-pipeline.mjs to catch other malformed entries","Check YAML indentation — careers_url must be a direct child of the entry"],"exampleFix":"// before (portals.yml)\n- name: Example Corp\n  provider: solidjobs\n# after\n- name: Example Corp\n  provider: solidjobs\n  careers_url: 'https://solid.jobs/public-api/offers'","handlingStrategy":"validation","validationCode":"if (!entry.careers_url || typeof entry.careers_url !== 'string') {\n  throw new Error(`entry ${entry.name}: solidjobs provider requires careers_url`);\n}","typeGuard":"function hasCareersUrl(entry) {\n  return typeof entry?.careers_url === 'string' && entry.careers_url.length > 0;\n}","tryCatchPattern":"try {\n  offers = await provider.fetch(entry, ctx);\n} catch (e) {\n  if (/careers_url required/.test(e.message)) {\n    console.warn(`${entry.name}: add careers_url to portals.yml`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["Require careers_url when adding any solidjobs entry","Copy full example entries when adding new companies","Run node doctor.mjs / verify-pipeline.mjs to catch missing fields early","Watch YAML indentation so nested keys don't silently vanish"],"tags":["config","missing-field","yaml"],"backgroundTag":"missing-required-config-field","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"}