{"record":{"id":"b56cce015df01d8d","repo":"can1357/oh-my-pi","slug":"request-aborted","errorCode":null,"errorMessage":"Request aborted","messagePattern":"Request aborted","errorType":"exception","errorClass":"AIError.AbortError","httpStatus":null,"severity":"warning","filePath":"packages/ai/src/providers/google-shared.ts","lineNumber":874,"sourceCode":"\tconst thinking = options.thinking;\n\tif (\n\t\tthinking &&\n\t\tmodel.reasoning &&\n\t\t(thinking.enabled || thinking.level !== undefined || thinking.budgetTokens !== undefined)\n\t) {\n\t\tconst cfg: ThinkingConfig = { includeThoughts: thinking.enabled && !options.hideThinkingSummary };\n\t\tif (thinking.level !== undefined) {\n\t\t\t// GoogleThinkingLevel mirrors the SDK's ThinkingLevel string enum values 1:1.\n\t\t\tcfg.thinkingLevel = thinking.level as ThinkingLevel;\n\t\t} else if (thinking.budgetTokens !== undefined) {\n\t\t\tcfg.thinkingBudget = thinking.budgetTokens;\n\t\t}\n\t\tconfig.thinkingConfig = cfg;\n\t}\n\n\tif (options.signal) {\n\t\tif (options.signal.aborted) {\n\t\t\tthrow new AIError.AbortError(\"Request aborted\");\n\t\t}\n\t\tconfig.abortSignal = options.signal;\n\t}\n\n\tif (options.cachedContent !== undefined) {\n\t\t// Blank names are never valid resource references; anything else stays\n\t\t// opaque so we do not invent format/model/project checks here.\n\t\tif (options.cachedContent.trim().length === 0) {\n\t\t\tthrow new AIError.ValidationError(\"cachedContent must not be blank\");\n\t\t}\n\t\tconst incompatibleFields = [\n\t\t\tconfig.systemInstruction !== undefined && \"systemInstruction\",\n\t\t\tconfig.tools !== undefined && \"tools\",\n\t\t\tconfig.toolConfig !== undefined && \"toolConfig\",\n\t\t].filter((field): field is string => Boolean(field));\n\t\tif (incompatibleFields.length > 0) {\n\t\t\tthrow new AIError.ValidationError(\n\t\t\t\t`cachedContent cannot be combined with request-level ${incompatibleFields.join(\", \")}`,","sourceCodeStart":856,"sourceCodeEnd":892,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/google-shared.ts#L856-L892","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check signal.aborted before calling and skip the request entirely","Create a fresh AbortController per request instead of reusing one","If intentional cancellation, catch AIError.AbortError and treat it as expected control flow","Adjust request lifecycle so the signal is created at dispatch time, not earlier"],"exampleFix":"// before\nconst controller = new AbortController();\nscheduleAbort(controller, timeoutMs); // may abort before send\nawait streamGoogle(model, params, { signal: controller.signal });\n// after\nconst controller = new AbortController();\nscheduleAbort(controller, timeoutMs);\nif (controller.signal.aborted) return; // skip cancelled work\nawait streamGoogle(model, params, { signal: controller.signal });","handlingStrategy":"validation","validationCode":"if (signal?.aborted) {\n  return; // skip the call entirely — no error to handle\n}","typeGuard":"function isLive(signal?: AbortSignal): boolean {\n  return !signal || !signal.aborted;\n}","tryCatchPattern":"try {\n  await streamGoogle(model, params, { signal });\n} catch (err) {\n  if (err instanceof AIError.AbortError) {\n    return null; // expected cancellation path\n  }\n  throw err;\n}","preventionTips":["Create a fresh AbortController per request; never reuse an already-aborted signal","Check signal.aborted before dispatching queued work","Distinguish intentional aborts from failures in your error handling","Wire request timeouts to fire after dispatch, not before"],"tags":["abort","signals","google","request-lifecycle"],"backgroundTag":"request-aborted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}