can1357/oh-my-pi · error

Failed to fetch item ${itemId}

Error message

Failed to fetch item ${itemId}

What it means

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.

Source

Thrown at packages/coding-agent/src/web/scrapers/hackernews.ts:149

	}

	return output;
}

export const handleHackerNews: SpecialHandler = async (url, timeout, signal) => {
	const parsed = new URL(url);
	if (!parsed.hostname.includes("news.ycombinator.com")) return null;

	const notes: string[] = [];
	let content = "";
	const fetchedAt = new Date().toISOString();

	try {
		const itemId = parsed.searchParams.get("id");

		if (itemId) {
			const item = await fetchItem(parseInt(itemId, 10), timeout, signal);
			if (!item) throw new Error(`Failed to fetch item ${itemId}`);

			content = await renderStory(item, timeout, 0, signal);
			notes.push(`Fetched HN item ${itemId} with top-level comments (depth 2)`);
		} else if (parsed.pathname === "/" || parsed.pathname === "/news") {
			const { content: raw, ok } = await loadPage(`${API_BASE}/topstories.json`, { timeout, signal });
			if (!ok) throw new Error("Failed to fetch top stories");
			const ids = tryParseJson<number[]>(raw);
			if (!ids) throw new Error("Failed to parse top stories");
			content = await renderListing(ids, timeout, "Hacker News - Top Stories", signal);
			notes.push("Fetched top 20 stories from HN front page");
		} else if (parsed.pathname === "/newest") {
			const { content: raw, ok } = await loadPage(`${API_BASE}/newstories.json`, { timeout, signal });
			if (!ok) throw new Error("Failed to fetch new stories");
			const ids = tryParseJson<number[]>(raw);
			if (!ids) throw new Error("Failed to parse new stories");
			content = await renderListing(ids, timeout, "Hacker News - New Stories", signal);
			notes.push("Fetched top 20 new stories");
		} else if (parsed.pathname === "/best") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify network connectivity to hacker-news.firebaseio.com and retry; transient failures are the most common cause.
  2. Check that the `id` query parameter is a valid numeric HN item id (Firebase returns null for unknown ids).
  3. Check the notes field of the returned result block for the underlying transport error reported by loadPage.
  4. If Firebase is down, fetch the item via https://hn.algolia.com/api/v1/items/<id> as an alternative API.
  5. Increase the tool's timeout if the failure was a timeout-induced ok:false.

Example fix

// before (caller treating error result as hard failure)
const res = await webFetch("https://news.ycombinator.com/item?id=999999999");
if (!res.ok) throw new Error(res.error);
// after: check notes and validate id before fetching
const id = new URL(url).searchParams.get("id");
if (!id || !/^\d+$/.test(id)) throw new Error(`invalid HN item id: ${id}`);
const res = await webFetch(url);
if (!res.ok) logger.warn("HN fetch failed", { notes: res.notes });
Defensive patterns

Strategy: validation

Validate before calling

const id = new URL(url).searchParams.get("id");
if (!id || !/^\d+$/.test(id)) throw new Error(`invalid HN item id: ${id}`);

Type guard

function isHnItem(item: unknown): item is { id: number; title?: string } {
  return typeof item === "object" && item !== null && "id" in item && typeof (item as { id: unknown }).id === "number";
}

Try / catch

try {
  const res = await webFetch(hnUrl);
} catch (err) {
  if (err instanceof ToolAbortError) throw err;
  logger.warn("HN item fetch failed, check network/Firebase", { url: hnUrl, err });
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/cd3662132eb732d5. Report an issue: GitHub.