{"record":{"id":"7b44c155e8c5d970","repo":"can1357/oh-my-pi","slug":"request-was-aborted-7b44c1","errorCode":null,"errorMessage":"Request was aborted.","messagePattern":"Request was aborted\\.","errorType":"exception","errorClass":"AbortError","httpStatus":null,"severity":"warning","filePath":"packages/ai/src/error/classes.ts","lineNumber":104,"sourceCode":"};\n\n/** Non-2xx response from the Anthropic API. */\nexport class AnthropicApiError extends ProviderHttpError {\n\tdeclare readonly headers: Headers;\n\treadonly requestId: string | null;\n\n\tconstructor(status: number, message: string, headers: Headers) {\n\t\tsuper(message, status, { headers });\n\t\tthis.name = \"AnthropicApiError\";\n\t\tthis.requestId = headers.get(\"request-id\");\n\t}\n\n\tstatic async fromResponse(response: Response, signal?: AbortSignal): Promise<AnthropicApiError> {\n\t\t// Avoid getReader() throwing when another consumer already owns the body.\n\t\tconst reader = response.body?.locked ? undefined : response.body?.getReader();\n\n\t\tif (!reader) {\n\t\t\tif (signal?.aborted) throw new AbortError(\"Request was aborted.\");\n\t\t\tconst detail = \"status code (no body)\";\n\t\t\treturn new AnthropicApiError(response.status, `${response.status} ${detail}`, response.headers);\n\t\t}\n\n\t\tlet aborted = false;\n\t\tlet timedOut = false;\n\t\tlet readerCancelled = false;\n\t\tconst cancelReader = () => {\n\t\t\tif (readerCancelled) return;\n\t\t\treaderCancelled = true;\n\t\t\tvoid reader.cancel().catch(() => {});\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tif (aborted) return;\n\t\t\taborted = true;\n\t\t\tcancelReader();\n\t\t};\n\t\tif (signal?.aborted) onAbort();","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/error/classes.ts#L86-L122","documentation":"An AbortError thrown by AnthropicApiError.fromResponse when the error-response body cannot be read (body locked by another consumer or missing) and the caller-supplied AbortSignal is already aborted. The library reports the abort as the real cause rather than surfacing a meaningless empty-body HTTP error.","triggerScenarios":"Passing an AbortSignal that is aborted before/while fromResponse reads an error response; reading the response body from two consumers so the reader is unavailable and the signal fires; request cancellation racing with a non-2xx response.","commonSituations":"User cancels a request in a UI at the same moment the API returns an error; a timeout AbortController fires during error-body download; sharing one Response body between logging and error handling.","solutions":["Check signal.aborted before treating this as an API failure — it is a cancellation, not a server error","Avoid aborting the signal while the error response is still being read; cancel only before the fetch resolves or after handling completes","Ensure only one consumer reads response.body; let fromResponse own it","Catch AbortError distinctly from AnthropicApiError to implement clean cancellation UX"],"exampleFix":"// before\nconst err = await AnthropicApiError.fromResponse(res, signal); // throws AbortError\n// after\ntry {\n  const err = await AnthropicApiError.fromResponse(res, signal);\n} catch (e) {\n  if (e instanceof AbortError || signal?.aborted) return handleCancelled();\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (signal?.aborted) return handleCancelled(); // check before calling fromResponse","typeGuard":"function isAbort(e: unknown): e is Error & { name: \"AbortError\" } {\n  return e instanceof Error && e.name === \"AbortError\";\n}","tryCatchPattern":"try {\n  const apiErr = await AnthropicApiError.fromResponse(res, signal);\n  handleApiError(apiErr);\n} catch (e) {\n  if (isAbort(e) || signal?.aborted) return; // cancellation, not API failure\n  throw e;\n}","preventionTips":["Check signal.aborted before interpreting any thrown error as an API failure","Give fromResponse exclusive ownership of response.body — don't read it elsewhere first","Abort only before fetch resolves or after response handling completes","Use distinct AbortControllers per request to avoid cross-request cancels"],"tags":["abort","cancellation","anthropic","http"],"backgroundTag":"request-aborted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}