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

  1. Verify manually that a current 'Ask HN: Who is hiring?' thread exists on news.ycombinator.com; if between threads, wait and re-run.
  2. Query the Algolia endpoint directly (hn.algolia.com/api/v1/search?...) to inspect what the search returns.
  3. Check resolveLatestThreadId in providers/hackernews.mjs against the actual response shape after any Algolia API change.
  4. 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

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


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)