{"record":{"id":"57550dacfa273b00","repo":"wavetermdev/waveterm","slug":"anthropic-s-s","errorCode":null,"errorMessage":"anthropic %s: %s","messagePattern":"anthropic (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-backend.go","lineNumber":403,"sourceCode":"// ---------- Public entrypoint ----------\n//\n// Mapping rules recap (Anthropic → AI‑SDK):\n// - message_start → AiMsgStart + AiMsgStartStep\n// - content_block_start(type=text) → AiMsgTextStart; text_delta → AiMsgTextDelta; content_block_stop → AiMsgTextEnd\n// - content_block_start(type=thinking) → AiMsgReasoningStart; thinking_delta → AiMsgReasoningDelta; stop → AiMsgReasoningEnd\n// - content_block_start(type=tool_use) → AiMsgToolInputStart; input_json_delta → AiMsgToolInputDelta; stop → AiMsgToolInputAvailable\n// - If final stop_reason == \"tool_use\": emit AiMsgFinishStep and return StopReason{Kind:ToolUse, ...} WITHOUT AiMsgFinish\n// - If message_stop with stop_reason == \"end_turn\" or nil: emit AiMsgFinish then [DONE]\n// - On Anthropic error event: AiMsgError and return StopKindError. :contentReference[oaicite:9]{index=9} :contentReference[oaicite:10]{index=10}\n\n// parseAnthropicHTTPError parses Anthropic API HTTP error responses\nfunc parseAnthropicHTTPError(resp *http.Response) error {\n\tslurp, _ := io.ReadAll(resp.Body)\n\n\t// Try to parse as Anthropic error format first\n\tvar eresp anthropicHTTPErrorResponse\n\tif err := json.Unmarshal(slurp, &eresp); err == nil && eresp.Error.Message != \"\" {\n\t\treturn sanitizeHostnameInError(fmt.Errorf(\"anthropic %s: %s\", resp.Status, eresp.Error.Message))\n\t}\n\n\t// Try to parse as proxy error format\n\tvar proxyErr uctypes.ProxyErrorResponse\n\tif err := json.Unmarshal(slurp, &proxyErr); err == nil && !proxyErr.Success && proxyErr.Error != \"\" {\n\t\treturn sanitizeHostnameInError(fmt.Errorf(\"anthropic %s: %s\", resp.Status, proxyErr.Error))\n\t}\n\n\t// Fall back to truncated raw response\n\tmsg := utilfn.TruncateString(strings.TrimSpace(string(slurp)), 120)\n\tif msg == \"\" {\n\t\tmsg = \"unknown error\"\n\t}\n\treturn sanitizeHostnameInError(fmt.Errorf(\"anthropic %s: %s\", resp.Status, msg))\n}\n\nfunc RunAnthropicChatStep(\n\tctx context.Context,","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-backend.go#L385-L421","documentation":"parseAnthropicHTTPError converts a non-2xx HTTP response into an error. When the response body parses as Anthropic's canonical error format ({\"error\":{\"type\":...,\"message\":...}}) it produces \"anthropic <status>: <message>\", sanitized of internal hostnames. This is the highest-fidelity error path — the message is the API's own explanation.","triggerScenarios":"Anthropic API (or proxy acting as Anthropic) returns non-2xx with body {\"error\":{\"message\":\"...\"}} — e.g. 401 invalid_api_key, 400 invalid_request_error, 429 rate_limit_error, 529 overloaded_error.","commonSituations":"Expired/invalid API keys (401); malformed requests or unsupported model names (400); rate limits (429); Anthropic capacity outages (529/500); billing issues.","solutions":["Read the message after the status code — Anthropic's error.message states the exact problem (auth, quota, model name, etc.).","For 401: rotate/fix the API key in configuration.","For 429: retry with exponential backoff honoring rate-limit headers (RateLimitInfo is tracked by this client).","For 529/500/overloaded: retry after a delay; these are transient upstream issues.","For 400: fix the request (model name, max_tokens vs thinking budget, tool schemas)."],"exampleFix":"// handling pattern\nstop, _, _, err := RunAnthropicChatStep(ctx, sse, opts, cont)\nif err != nil {\n    var apiErr *apiError\n    if strings.Contains(err.Error(), \"429\") {\n        time.Sleep(backoff) ; retry(ctx)\n    }\n}","handlingStrategy":"retry","validationCode":"// pre-flight: ensure key/model are set before calling\nif apiKey == \"\" { return errors.New(\"missing ANTHROPIC API key\") }\nif !validModelName(model) { return fmt.Errorf(\"unknown model %q\", model) }","typeGuard":null,"tryCatchPattern":"err := runAnthropic(ctx)\nif err != nil {\n    switch {\n    case strings.Contains(err.Error(), \"401\"):\n        return refreshAPIKey(ctx)\n    case strings.Contains(err.Error(), \"429\"):\n        return retryWithBackoff(ctx, honorRateLimitInfo)\n    case strings.Contains(err.Error(), \"529\"), strings.Contains(err.Error(), \"overloaded\"):\n        return retryWithBackoff(ctx, nil)\n    default:\n        return err\n    }\n}","preventionTips":["Validate API key and model name before requests","Implement exponential backoff honoring RateLimitInfo for 429","Handle 529/overloaded as transient with retries","Keep max_tokens and thinking budgets within API limits to avoid 400s"],"tags":["http","anthropic","api-error","rate-limit"],"backgroundTag":"anthropic-api-error","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}