can1357/oh-my-pi · info · ToolAbortError

Aborted

Error message

Aborted

What it means

loadPage, the shared fetch helper for all web-fetch special handlers, checks `signal?.aborted` at the top of each user-agent retry iteration and throws ToolAbortError immediately if the caller's signal is already cancelled. This is deliberate: a fetch would still be attempted with an aborted combined signal, so the helper short-circuits with the canonical tool-cancellation error.

Source

Thrown at packages/coding-agent/src/web/scrapers/types.ts:145

			return new TextDecoder(label as Bun.Encoding).decode(bytes);
		} catch {
			// Unknown/unsupported label — fall back to UTF-8.
		}
	}
	return bytes.toString("utf-8");
}

/**
 * Fetch a page with timeout and size limit
 */
export async function loadPage(url: string, options: LoadPageOptions = {}): Promise<LoadPageResult> {
	const { timeout = 20, headers = {}, maxBytes = MAX_BYTES, signal, method = "GET", body } = options;

	let lastError: string | undefined;
	let retried429 = false;
	for (let attempt = 0; attempt < USER_AGENTS.length; attempt++) {
		if (signal?.aborted) {
			throw new ToolAbortError();
		}

		const userAgent = USER_AGENTS[attempt];
		const requestSignal = ptree.combineSignals(signal, timeout * 1000);

		try {
			const requestInit: RequestInit = {
				signal: requestSignal,
				method,
				headers: {
					"User-Agent": userAgent,
					Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
					"Accept-Language": "en-US,en;q=0.5",
					"Accept-Encoding": "identity", // Cloudflare Markdown-for-Agents returns corrupted bytes when compression is negotiated
					...headers,
				},
				redirect: "follow",
			};

View on GitHub (pinned to 9690622007)

Solutions

  1. No fix needed — this is intentional cancellation; catch ToolAbortError in the caller and stop work.
  2. If it fires without user action, audit the AbortSignal source for premature aborts (short timeouts, leaked controllers aborted elsewhere).
  3. Increase the timeout option passed to loadPage/web-fetch so long fetches complete before the combined timeout signal fires.
  4. Avoid reusing an already-aborted AbortController across queued fetches; create a fresh controller per request.
  5. Check callers for aborting a shared controller on one failed request while other requests still need it.

Example fix

// before: one shared controller for a batch; one abort kills every loadPage call
const controller = new AbortController();
for (const url of urls) await loadPage(url, { signal: controller.signal });
// after: per-request controller with its own timeout
for (const url of urls) {
  const controller = new AbortController();
  try {
    await loadPage(url, { signal: controller.signal, timeout: 20 });
  } catch (err) {
    if (err instanceof ToolAbortError) break; // only this request's signal fired
    throw err;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (signal?.aborted) return; // avoid calling loadPage with a pre-aborted signal

Type guard

function isAbort(err: unknown): err is ToolAbortError {
  return err instanceof ToolAbortError;
}

Try / catch

try {
  const page = await loadPage(url, { signal, timeout: 20 });
} catch (err) {
  if (err instanceof ToolAbortError) return; // cancellation — stop, don't retry
  throw err;
}

Prevention

When it happens

Trigger: Any handler (result, response, altResult, readmeResult, postResult, topicResult — e.g. GitHub, Reddit, HN scrapers) calling loadPage after its AbortSignal has fired: user pressed Esc, a timeout elapsed, session shutdown, or a 429 backoff wait was interrupted. Also fires when the signal aborts between user-agent attempts during bot-block retry rotation.

Common situations: User cancels a slow page fetch mid user-agent rotation; combined timeout signal (timeout*1000 via ptree.combineSignals) expires between attempts; Retry-After backoff for a 429 overlaps signal abort; upstream very slow so users cancel routinely.

Related errors


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