santifer/career-ops · error
hackernews: could not find "Ask HN: Who is hiring?" thread…
Error message
hackernews: could not find "Ask HN: Who is hiring?" thread in search results
What it means
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'.
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.
Example fix
// before
const threadId = resolveLatestThreadId(searchData);
// after (log what was actually seen)
const threadId = resolveLatestThreadId(searchData);
if (!threadId) console.error('hn: search hits:', searchData?.hits?.length, searchData?.hits?.[0]?.title); Defensive patterns
Strategy: retry
Validate before calling
function hasHiringThread(searchData) {
return Array.isArray(searchData?.hits) && searchData.hits.some(h => /who is hiring/i.test(h?.title ?? ''));
}
if (!hasHiringThread(await (await fetch(SEARCH_URL)).json())) console.warn('no current Who-is-hiring thread'); Type guard
const hasHits = (v) => Array.isArray(v?.hits) && v.hits.length > 0;
Try / catch
try {
await provider.fetch(entry, ctx);
} catch (err) {
if (err.message.includes('could not find \"Ask HN: Who is hiring?\"')) {
console.error('HN search returned no hiring thread — retrying later');
return scheduleRetry({ inHours: 24 });
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- hackernews: unexpected item response for thread
- glints: unexpected API response
- 4dayweek: unexpected API response on page
- a16z-speedrun-talent: unexpected API response on page
- agentic-jobs: parsed 0 jobs from the API — the response…
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/35ab688c10e199bb.
Report an issue: GitHub.
Appendix: source
Thrown at providers/hackernews.mjs:146
const RE = /ask\s+hn[:\s]+who\s+is\s+hiring/i;
for (const hit of hits) {
if (hit && typeof hit.objectID === 'string' && typeof hit.title === 'string') {
if (RE.test(hit.title)) return hit.objectID;
}
}
return null;
}
/** @type {Provider} */
export default {
id: 'hackernews',
async fetch(entry, ctx) {
// Step 1: Find the latest "Who is hiring?" story id.
const searchData = await ctx.fetchJson(SEARCH_URL, { redirect: 'error' });
const threadId = resolveLatestThreadId(searchData);
if (!threadId) {
throw new Error('hackernews: could not find "Ask HN: Who is hiring?" thread in search results');
}
const threadHnUrl = `https://news.ycombinator.com/item?id=${threadId}`;
// Step 2: Fetch the thread item (children = top-level job comments).
const item = await ctx.fetchJson(itemUrl(threadId), { redirect: 'error' });
if (!item || typeof item !== 'object') {
throw new Error(`hackernews: unexpected item response for thread ${threadId}`);
}
const children = /** @type {any} */ (item).children;
if (!Array.isArray(children)) return [];
// Step 3: Parse each comment.
const jobs = [];
for (const child of children) {
// Skip deleted / dead / empty comments.
if (!child || child.deleted || child.dead) continue;View on GitHub (pinned to aac998c7ed)