can1357/oh-my-pi · warning · AIError.AbortError

Request aborted

Error message

Request aborted

What it means

buildGoogleGenerateContentParams throws AIError.AbortError("Request aborted") when the AbortSignal passed in options.signal is already aborted at request-build time. The library checks the signal before sending so an already-cancelled request never reaches the network.

Source

Thrown at packages/ai/src/providers/google-shared.ts:874

	const thinking = options.thinking;
	if (
		thinking &&
		model.reasoning &&
		(thinking.enabled || thinking.level !== undefined || thinking.budgetTokens !== undefined)
	) {
		const cfg: ThinkingConfig = { includeThoughts: thinking.enabled && !options.hideThinkingSummary };
		if (thinking.level !== undefined) {
			// GoogleThinkingLevel mirrors the SDK's ThinkingLevel string enum values 1:1.
			cfg.thinkingLevel = thinking.level as ThinkingLevel;
		} else if (thinking.budgetTokens !== undefined) {
			cfg.thinkingBudget = thinking.budgetTokens;
		}
		config.thinkingConfig = cfg;
	}

	if (options.signal) {
		if (options.signal.aborted) {
			throw new AIError.AbortError("Request aborted");
		}
		config.abortSignal = options.signal;
	}

	if (options.cachedContent !== undefined) {
		// Blank names are never valid resource references; anything else stays
		// opaque so we do not invent format/model/project checks here.
		if (options.cachedContent.trim().length === 0) {
			throw new AIError.ValidationError("cachedContent must not be blank");
		}
		const incompatibleFields = [
			config.systemInstruction !== undefined && "systemInstruction",
			config.tools !== undefined && "tools",
			config.toolConfig !== undefined && "toolConfig",
		].filter((field): field is string => Boolean(field));
		if (incompatibleFields.length > 0) {
			throw new AIError.ValidationError(
				`cachedContent cannot be combined with request-level ${incompatibleFields.join(", ")}`,

View on GitHub (pinned to 9690622007)

Solutions

  1. Check signal.aborted before calling and skip the request entirely
  2. Create a fresh AbortController per request instead of reusing one
  3. If intentional cancellation, catch AIError.AbortError and treat it as expected control flow
  4. Adjust request lifecycle so the signal is created at dispatch time, not earlier

Example fix

// before
const controller = new AbortController();
scheduleAbort(controller, timeoutMs); // may abort before send
await streamGoogle(model, params, { signal: controller.signal });
// after
const controller = new AbortController();
scheduleAbort(controller, timeoutMs);
if (controller.signal.aborted) return; // skip cancelled work
await streamGoogle(model, params, { signal: controller.signal });
Defensive patterns

Strategy: validation

Validate before calling

if (signal?.aborted) {
  return; // skip the call entirely — no error to handle
}

Type guard

function isLive(signal?: AbortSignal): boolean {
  return !signal || !signal.aborted;
}

Try / catch

try {
  await streamGoogle(model, params, { signal });
} catch (err) {
  if (err instanceof AIError.AbortError) {
    return null; // expected cancellation path
  }
  throw err;
}

Prevention

When it happens

Trigger: Caller passes an AbortSignal that was aborted before streamGoogleGenAI/params was invoked — e.g. a request created after its controlling request/timeout already fired, or a signal aborted synchronously before the call.

Common situations: Race between a request timeout firing and the API call being made; reusing a controller whose abort() already ran; queueing requests and aborting stale ones before they execute.

Related errors


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