can1357/oh-my-pi · warning · ToolAbortError

Aborted

Error message

Aborted

What it means

readResponseWithLimit streams the response body chunk-by-chunk and checks the AbortSignal before every read. When the signal is aborted it cancels the reader and throws ToolAbortError('Aborted') so body reads don't hang past cancellation. This propagates the caller's cancellation into the body-streaming phase.

Source

Thrown at packages/coding-agent/src/web/scrapers/utils.ts:42

	ok: true;
	buffer: Uint8Array;
	contentDisposition?: string;
}

export type BinaryFetchResult = BinaryFetchSuccess | { ok: false; error?: string };

async function readResponseWithLimit(response: Response, maxBytes: number, signal?: AbortSignal): Promise<Uint8Array> {
	const reader = response.body?.getReader();
	if (!reader) return new Uint8Array(0);

	const chunks: Buffer[] = [];
	let totalBytes = 0;

	try {
		while (true) {
			if (signal?.aborted) {
				await reader.cancel();
				throw new ToolAbortError();
			}
			const { done, value } = await reader.read();
			if (done) break;
			if (!value || value.byteLength === 0) continue;

			totalBytes += value.byteLength;
			if (totalBytes > maxBytes) {
				await reader.cancel();
				throw new Error(`response exceeds ${maxBytes} bytes`);
			}

			chunks.push(Buffer.from(value));
		}
	} finally {
		reader.releaseLock();
	}

	return new Uint8Array(Buffer.concat(chunks, totalBytes));

View on GitHub (pinned to 9690622007)

Solutions

  1. Raise the caller's timeout if large payloads legitimately take long.
  2. Catch ToolAbortError around readResponseWithLimit/fetchBinary and treat as cancellation.
  3. Verify upstream code isn't aborting early (e.g. a race with an already-resolved promise).
  4. Pass undefined signal only if you truly want an unabortable read — otherwise keep the signal and handle the error.

Example fix

// before
const buf = await readResponseWithLimit(res, MAX, signal);
// after
let buf;
try { buf = await readResponseWithLimit(res, MAX, signal); }
catch (e) { if (e instanceof ToolAbortError) return { ok: false, error: "aborted" }; throw e; }
Defensive patterns

Strategy: try-catch

Validate before calling

// Nothing to pre-validate; optionally abort early:
if (signal?.aborted) return { ok: false, error: "aborted" };

Type guard

import { ToolAbortError } from "../tools/tool-errors"; const isToolAbortError = (e: unknown): e is ToolAbortError => e instanceof ToolAbortError;

Try / catch

try { const buf = await readResponseWithLimit(res, MAX, signal); }
catch (e) {
  if (isToolAbortError(e)) return { ok: false, error: "aborted" };
  if (e instanceof Error && /response exceeds/.test(e.message)) return { ok: false, error: "too-large" };
  throw e;
}

Prevention

When it happens

Trigger: AbortSignal fires while the response body is still being streamed into the size-capped reader loop — the next loop iteration detects signal.aborted.

Common situations: Large/slow downloads (PDFs, images) cancelled by a tool timeout or user abort halfway through the body.

Related errors


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