santifer/career-ops · error · Error

hackernews: unexpected item response for thread ${threadId}

Error message

hackernews: unexpected item response for thread ${threadId}

What it means

Thrown by hackernews fetch() when the Algolia items API returns a response for the resolved thread id that is null or not a plain object. A valid thread item is an object with a children[] array; anything else means the item endpoint returned an unexpected payload for that threadId.

Source

Thrown at providers/hackernews.mjs:161

/** @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;
      const text = typeof child.text === 'string' ? child.text : '';
      if (!text.trim()) continue;

      const parsed = parseHnComment(text, threadHnUrl);
      if (!parsed) continue;

      jobs.push({
        title: parsed.title,

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Retry the scan — if the threadId was transiently bad, the next run resolves a fresh one.
  2. Confirm the item endpoint manually: open https://hn.algolia.com/api/v1/items/{threadId} and check it returns an object with children[].
  3. If the thread is genuinely dead/deleted, wait for the next monthly thread (this is a data condition, not a code bug).
  4. Ensure ctx.fetchJson surfaces non-2xx as errors rather than returning parsed error bodies.
Defensive patterns

Strategy: retry

Validate before calling

// After resolving threadId, sanity-check the item before relying on it.
const item = await ctx.fetchJson(itemUrl(threadId), { redirect: 'error' });
if (!item || typeof item !== 'object') {
  throw new Error(`hackernews: thread ${threadId} returned a non-object item — likely dead/deleted`);
}

Type guard

/** A valid Algolia item response: an object (children[] checked separately). */
function isHnItem(value) {
  return !!value && typeof value === 'object' && !Array.isArray(value);
}

Try / catch

try {
  return await hackernewsProvider.fetch(entry, ctx);
} catch (err) {
  if (/unexpected item response for thread/.test(err.message)) {
    // The threadId may point at a now-deleted story; a fresh run resolves a new id.
    console.warn(`hackernews: transient item response — will retry next scan`);
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: The threadId resolved from step 1 points at a story that has since been deleted/dead on HN (Algolia returns null for dead items); a transient Algolia 200-with-null; the items endpoint rate-limited and returned a non-object error body; the id is valid as a search hit but stale by the time the item fetch runs.

Common situations: A thread flagged/deleted shortly after posting; Algolia eventual-consistency lag between the search index and the items index; rate limiting after many sequential scans; an intermediary returning an HTML error page that parsed to a non-object.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/6210d5140243ca7a. Report an issue: GitHub.