{"record":{"id":"a342803687ebd7d4","repo":"can1357/oh-my-pi","slug":"an-unknown-error-occurred-a34280","errorCode":null,"errorMessage":"An unknown error occurred","messagePattern":"An unknown error occurred","errorType":"exception","errorClass":"ProviderResponseError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/anthropic.ts","lineNumber":2695,"sourceCode":"\t\t\t\t\t\t// A stop_reason arrived via message_delta, so generation finished;\n\t\t\t\t\t\t// only the trailing message_stop frame is missing (non-conforming\n\t\t\t\t\t\t// gateway). Degrade to best-effort instead of discarding the turn.\n\t\t\t\t\t\treportAnthropicEnvelopeAnomaly(\"stream ended before message_stop\");\n\t\t\t\t\t}\n\t\t\t\t\tif (openBlocks.size > 0) {\n\t\t\t\t\t\tfor (const [openIndex, openBlock] of openBlocks) {\n\t\t\t\t\t\t\treportAnthropicEnvelopeAnomaly(\n\t\t\t\t\t\t\t\t`stream ended with an unterminated ${openBlock.kind} block at index ${openIndex}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (openBlock.kind === \"ignored\" || openBlock.contentIndex < 0) continue;\n\t\t\t\t\t\t\tconst danglingBlock = blocks[openBlock.contentIndex];\n\t\t\t\t\t\t\tif (danglingBlock) finalizeStreamBlock(danglingBlock, openBlock.contentIndex);\n\t\t\t\t\t\t}\n\t\t\t\t\t\topenBlocks.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\t\t\tthrow new AIError.ProviderResponseError(output.errorMessage ?? \"An unknown error occurred\", {\n\t\t\t\t\t\t\tprovider: model.provider,\n\t\t\t\t\t\t\tkind: \"output\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (streamError) {\n\t\t\t\t\tconst streamFailure = activeAbortTracker.getLocalAbortReason() ?? streamError;\n\t\t\t\t\tif (\n\t\t\t\t\t\t!disableStrictTools &&\n\t\t\t\t\t\tfirstTokenTime === undefined &&\n\t\t\t\t\t\thasStrictAnthropicTools(params) &&\n\t\t\t\t\t\tAIError.isGrammarError(streamFailure)\n\t\t\t\t\t) {\n\t\t\t\t\t\t// Log-only: the retried turn must not carry an errorMessage on\n\t\t\t\t\t\t// success (consumers treat its presence as failure).\n\t\t\t\t\t\tlogger.warn(\"anthropic: strict tools rejected, retrying without strict tools\", {\n\t\t\t\t\t\t\tmodel: model.id,\n\t\t\t\t\t\t\terror: await finalizeErrorMessage(streamFailure, rawRequestDump),","sourceCodeStart":2677,"sourceCodeEnd":2713,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/anthropic.ts#L2677-L2713","documentation":"AIError.ProviderResponseError is thrown in the Anthropic streaming provider when a completed stream ends with output.stopReason of 'aborted' or 'error'. The provider ran the request but the upstream returned a terminal error state instead of normal completion; output.errorMessage is used when the API supplied one, otherwise the generic 'An unknown error occurred' fallback is used. It carries kind:'output' and the provider name so callers can distinguish provider-side failures from request-configuration failures.","triggerScenarios":"Calling an Anthropic model (streaming path in packages/ai/src/providers/anthropic.ts) when the stream finishes with stopReason 'aborted' or 'error' and no errorMessage text was captured — e.g. the upstream aborted mid-generation, an overloaded_error or other API error event terminated the stream without a parsed message, or an internal error event carried no payload.","commonSituations":"Anthropic API returns overloaded_error (529) or a transient internal error mid-stream; a proxy/gateway truncates the stream; the request was cancelled upstream and surfaced as 'aborted' with no message; SDK version where the error event's message field isn't parsed into output.errorMessage, so only the fallback string appears.","solutions":["Inspect the full output object / provider logs around the throw to find the real upstream stopReason and any error event payload, since 'An unknown error occurred' hides the cause","Retry the request with backoff if stopReason was 'error' due to overloaded_error or a transient Anthropic incident (check status.anthropic.com)","Catch AIError.ProviderResponseError, check the kind==='output' and provider fields, and surface a retryable failure to your app instead of crashing","If aborted unexpectedly, check for request cancellation (AbortSignal) or gateway/proxy timeouts in your infrastructure","Update @oh-my-pi/pi-ai in case the missing errorMessage parsing has been fixed in a newer version"],"exampleFix":"// before: raw crash on transient overload\nconst result = await session.prompt(model, messages);\n// after: catch provider output errors and retry\ntry {\n  const result = await session.prompt(model, messages);\n} catch (err) {\n  if (err instanceof AIError.ProviderResponseError && err.kind === \"output\") {\n    // log err.provider + stopReason, retry with backoff\n  }\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isProviderOutputError(err: unknown): err is AIError.ProviderResponseError {\n  return err instanceof AIError.ProviderResponseError && err.kind === \"output\";\n}","tryCatchPattern":"try {\n  const result = await session.prompt(model, messages);\n} catch (err) {\n  if (err instanceof AIError.ProviderResponseError && err.kind === \"output\") {\n    logger.warn(\"anthropic stream ended in error state\", { provider: err.provider });\n    // retry with backoff or surface a user-facing failure\n    return;\n  }\n  throw err;\n}","preventionTips":["Always wrap streaming provider calls in try/catch for AIError.ProviderResponseError","Implement automatic retry with exponential backoff for kind==='output' errors, which are frequently transient (overloaded_error)","Monitor Anthropic status and set a lower concurrency during incidents","Check AbortSignal handling so intentional cancellations are not mistaken for provider errors","Keep the SDK updated so upstream error payloads are parsed into output.errorMessage"],"tags":["anthropic","streaming","provider-error","api"],"backgroundTag":"provider-stream-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}