{"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":"Error","httpStatus":null,"severity":"error","filePath":"providers/hackernews.mjs","lineNumber":153,"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":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/hackernews.mjs#L135-L171","documentation":"Thrown by hackernews fetch() when resolveLatestThreadId(searchData) returns null after querying the Algolia HN search API (filtered to tags=story,author_whoishiring). It means the search returned no usable monthly 'Ask HN: Who is hiring?' thread id. The provider searches by the whoishiring account tag precisely so a free-text query cannot surface an unrelated story; a null result therefore points at a data/availability problem, not a ranking problem.","triggerScenarios":"Algolia's HN index is lagging or temporarily unavailable (5xx/empty hits); it is the very start of a month before the whoishiring account has posted the new thread; a network/timeout error returned a 200 with a degenerate body; the Algolia tags query changed behaviour. Since the lookup is filtered to the whoishiring author, a null hit means that author's thread genuinely is not in the returned hits.","commonSituations":"Running a scan in the first days of a new month before the thread is live; transient Algolia outage or rate limiting; a corporate proxy returning an HTML interstitial parsed as JSON; DNS/connectivity failure to hn.algolia.com.","solutions":["Retry on the next scan run (the thread typically appears within the first few days of the month).","Verify the search endpoint directly: open https://hn.algolia.com/api/v1/search_by_date?tags=story,author_whoishiring&hitsPerPage=5 in a browser and confirm hits[] is populated.","If Algolia is persistently down, temporarily disable the hackernews board entry (enabled: false) until it recovers.","Check that ctx.fetchJson is not silently returning a cached/empty body from a proxy."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Probe the Algolia search endpoint before driving the provider.\nconst probe = await ctx.fetchJson(SEARCH_URL, { redirect: 'error' });\nif (!Array.isArray(probe?.hits) || probe.hits.length === 0) {\n  console.warn('hackernews: search index unavailable this run — skipping');\n  continue;\n}","typeGuard":"/** Algolia search response carrying at least one whoishiring story. */\nfunction hasHiringHits(json) {\n  return !!json && typeof json === 'object'\n    && Array.isArray(json.hits) && json.hits.length > 0\n    && json.hits.some(h => h && typeof h.objectID === 'string');\n}","tryCatchPattern":"for (let attempt = 0; attempt < 2; attempt++) {\n  try {\n    return await hackernewsProvider.fetch(entry, ctx);\n  } catch (err) {\n    const transient = /could not find .* thread in search results/.test(err.message);\n    if (!transient || attempt === 1) throw err;\n    await new Promise(r => setTimeout(r, 5000)); // back off, then retry once\n  }\n}","preventionTips":["Expect this to fire in the first days of a month before the new thread is live — treat as transient, not a bug.","Verify the Algolia search URL in a browser when the error persists to distinguish outage from data lag.","Do not widen resolveLatestThreadId's acceptance criteria — that reintroduces the free-text false-positive problem the author filter was added to fix."],"tags":["network","external-api","hackernews","transient"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}