{"record":{"id":"cd3662132eb732d5","repo":"can1357/oh-my-pi","slug":"failed-to-fetch-item-itemid","errorCode":null,"errorMessage":"Failed to fetch item ${itemId}","messagePattern":"Failed to fetch item (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/web/scrapers/hackernews.ts","lineNumber":149,"sourceCode":"\t}\n\n\treturn output;\n}\n\nexport const handleHackerNews: SpecialHandler = async (url, timeout, signal) => {\n\tconst parsed = new URL(url);\n\tif (!parsed.hostname.includes(\"news.ycombinator.com\")) return null;\n\n\tconst notes: string[] = [];\n\tlet content = \"\";\n\tconst fetchedAt = new Date().toISOString();\n\n\ttry {\n\t\tconst itemId = parsed.searchParams.get(\"id\");\n\n\t\tif (itemId) {\n\t\t\tconst item = await fetchItem(parseInt(itemId, 10), timeout, signal);\n\t\t\tif (!item) throw new Error(`Failed to fetch item ${itemId}`);\n\n\t\t\tcontent = await renderStory(item, timeout, 0, signal);\n\t\t\tnotes.push(`Fetched HN item ${itemId} with top-level comments (depth 2)`);\n\t\t} else if (parsed.pathname === \"/\" || parsed.pathname === \"/news\") {\n\t\t\tconst { content: raw, ok } = await loadPage(`${API_BASE}/topstories.json`, { timeout, signal });\n\t\t\tif (!ok) throw new Error(\"Failed to fetch top stories\");\n\t\t\tconst ids = tryParseJson<number[]>(raw);\n\t\t\tif (!ids) throw new Error(\"Failed to parse top stories\");\n\t\t\tcontent = await renderListing(ids, timeout, \"Hacker News - Top Stories\", signal);\n\t\t\tnotes.push(\"Fetched top 20 stories from HN front page\");\n\t\t} else if (parsed.pathname === \"/newest\") {\n\t\t\tconst { content: raw, ok } = await loadPage(`${API_BASE}/newstories.json`, { timeout, signal });\n\t\t\tif (!ok) throw new Error(\"Failed to fetch new stories\");\n\t\t\tconst ids = tryParseJson<number[]>(raw);\n\t\t\tif (!ids) throw new Error(\"Failed to parse new stories\");\n\t\t\tcontent = await renderListing(ids, timeout, \"Hacker News - New Stories\", signal);\n\t\t\tnotes.push(\"Fetched top 20 new stories\");\n\t\t} else if (parsed.pathname === \"/best\") {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/scrapers/hackernews.ts#L131-L167","documentation":"The Hacker News special handler in the web-fetch pipeline throws this when `fetchItem()` returns null for a given `?id=` parameter. `fetchItem` returns null either because `loadPage` reported `ok: false` (network failure, HTTP error, bot-block) or because `tryParseJson` could not decode the Firebase API response body. The error is caught by handleHackerNews and surfaced as an error result block rather than crashing the tool.","triggerScenarios":"Calling the web-fetch tool with a news.ycombinator.com item URL (e.g. https://news.ycombinator.com/item?id=12345) where the GET to https://hacker-news.firebaseio.com/v0/item/<id>.json fails or returns a non-JSON body. Also fires for a non-numeric id parse (parseInt yields NaN, Firebase 404s) or a deleted/deleted-unknown item id.","commonSituations":"Offline or DNS-broken environment; HN Firebase API rate-limiting or outage; corporate proxy stripping the response; malformed/nonexistent item id (e.g. trailing garbage in id param); very old or deleted item that returns literal `null` from Firebase, which tryParseJson decodes to null.","solutions":["Verify network connectivity to hacker-news.firebaseio.com and retry; transient failures are the most common cause.","Check that the `id` query parameter is a valid numeric HN item id (Firebase returns null for unknown ids).","Check the notes field of the returned result block for the underlying transport error reported by loadPage.","If Firebase is down, fetch the item via https://hn.algolia.com/api/v1/items/<id> as an alternative API.","Increase the tool's timeout if the failure was a timeout-induced ok:false."],"exampleFix":"// before (caller treating error result as hard failure)\nconst res = await webFetch(\"https://news.ycombinator.com/item?id=999999999\");\nif (!res.ok) throw new Error(res.error);\n// after: check notes and validate id before fetching\nconst id = new URL(url).searchParams.get(\"id\");\nif (!id || !/^\\d+$/.test(id)) throw new Error(`invalid HN item id: ${id}`);\nconst res = await webFetch(url);\nif (!res.ok) logger.warn(\"HN fetch failed\", { notes: res.notes });","handlingStrategy":"validation","validationCode":"const id = new URL(url).searchParams.get(\"id\");\nif (!id || !/^\\d+$/.test(id)) throw new Error(`invalid HN item id: ${id}`);","typeGuard":"function isHnItem(item: unknown): item is { id: number; title?: string } {\n  return typeof item === \"object\" && item !== null && \"id\" in item && typeof (item as { id: unknown }).id === \"number\";\n}","tryCatchPattern":"try {\n  const res = await webFetch(hnUrl);\n} catch (err) {\n  if (err instanceof ToolAbortError) throw err;\n  logger.warn(\"HN item fetch failed, check network/Firebase\", { url: hnUrl, err });\n}","preventionTips":["Validate the `id` query parameter is numeric before fetching.","Pre-probe hacker-news.firebaseio.com health before batch HN fetches.","Read the result block's notes field for underlying loadPage transport errors.","Have an Algolia HN API fallback ready.","Set a realistic timeout; HN Firebase is usually fast, so long delays indicate network problems."],"tags":["network","http","hacker-news","api"],"backgroundTag":"fetch-failed-non-ok-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}