{"record":{"id":"35ab688c10e199bb","repo":"santifer/career-ops","slug":"hackernews-could-not-find-ask-hn-who-is-hiring","errorCode":null,"errorMessage":"hackernews: could not find \"Ask HN: Who is hiring?\" thread in search results","messagePattern":"hackernews: could not find \"Ask HN: Who is hiring\\?\" thread in search results","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/hackernews.mjs","lineNumber":146,"sourceCode":"  const RE = /ask\\s+hn[:\\s]+who\\s+is\\s+hiring/i;\n  for (const hit of hits) {\n    if (hit && typeof hit.objectID === 'string' && typeof hit.title === 'string') {\n      if (RE.test(hit.title)) return hit.objectID;\n    }\n  }\n  return null;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'hackernews',\n\n  async fetch(entry, ctx) {\n    // Step 1: Find the latest \"Who is hiring?\" story id.\n    const searchData = await ctx.fetchJson(SEARCH_URL, { redirect: 'error' });\n    const threadId = resolveLatestThreadId(searchData);\n    if (!threadId) {\n      throw new Error('hackernews: could not find \"Ask HN: Who is hiring?\" thread in search results');\n    }\n\n    const threadHnUrl = `https://news.ycombinator.com/item?id=${threadId}`;\n\n    // Step 2: Fetch the thread item (children = top-level job comments).\n    const item = await ctx.fetchJson(itemUrl(threadId), { redirect: 'error' });\n    if (!item || typeof item !== 'object') {\n      throw new Error(`hackernews: unexpected item response for thread ${threadId}`);\n    }\n\n    const children = /** @type {any} */ (item).children;\n    if (!Array.isArray(children)) return [];\n\n    // Step 3: Parse each comment.\n    const jobs = [];\n    for (const child of children) {\n      // Skip deleted / dead / empty comments.\n      if (!child || child.deleted || child.dead) continue;","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/hackernews.mjs#L128-L164","documentation":"The Hacker News provider first searches Algolia (SEARCH_URL) for the latest 'Ask HN: Who is hiring?' story. If resolveLatestThreadId cannot find a matching story in the search results, fetch() throws this error before any comments are parsed. It exists so a silent empty scan is never mistaken for 'no jobs posted'.","triggerScenarios":"The Algolia search response contains no story matching the 'Ask HN: Who is hiring?' title pattern — empty hits array, API error JSON, or the monthly thread not yet posted/retitled.","commonSituations":"Running during the gap between monthly threads, Algolia HN Search API outage or schema change, or a network middlebox returning non-search JSON that resolves to no thread.","solutions":["Verify manually that a current 'Ask HN: Who is hiring?' thread exists on news.ycombinator.com; if between threads, wait and re-run.","Query the Algolia endpoint directly (hn.algolia.com/api/v1/search?...) to inspect what the search returns.","Check resolveLatestThreadId in providers/hackernews.mjs against the actual response shape after any Algolia API change.","Retry later — transient Algolia failures commonly present as empty hit lists."],"exampleFix":"// before\nconst threadId = resolveLatestThreadId(searchData);\n// after (log what was actually seen)\nconst threadId = resolveLatestThreadId(searchData);\nif (!threadId) console.error('hn: search hits:', searchData?.hits?.length, searchData?.hits?.[0]?.title);","handlingStrategy":"retry","validationCode":"function hasHiringThread(searchData) {\n  return Array.isArray(searchData?.hits) && searchData.hits.some(h => /who is hiring/i.test(h?.title ?? ''));\n}\nif (!hasHiringThread(await (await fetch(SEARCH_URL)).json())) console.warn('no current Who-is-hiring thread');","typeGuard":"const hasHits = (v) => Array.isArray(v?.hits) && v.hits.length > 0;","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (err.message.includes('could not find \\\"Ask HN: Who is hiring?\\\"')) {\n    console.error('HN search returned no hiring thread — retrying later');\n    return scheduleRetry({ inHours: 24 });\n  }\n  throw err;\n}","preventionTips":["Schedule HN scans shortly after the thread posts (start of month) to avoid the gap window.","Retry with backoff — Algolia outages present as empty hit lists.","Verify the search URL and title regex after any Algolia API change.","Log hits.length when the thread is missing to distinguish outage from true absence."],"tags":["api","hackernews","search","scraper"],"backgroundTag":"resource-not-found","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"}