{"record":{"id":"b903c2b0d2a354f2","repo":"micro/go-micro","slug":"stream-api-error-s-s","errorCode":null,"errorMessage":"stream API error (%s): %s","messagePattern":"stream API error \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ai/anthropic/anthropic.go","lineNumber":268,"sourceCode":"\n\tapiURL := strings.TrimRight(p.opts.BaseURL, \"/\") + \"/v1/messages\"\n\thttpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewReader(reqBody))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create stream request: %w\", err)\n\t}\n\thttpReq.Header.Set(\"Content-Type\", \"application/json\")\n\thttpReq.Header.Set(\"Accept\", \"text/event-stream\")\n\thttpReq.Header.Set(\"x-api-key\", p.opts.APIKey)\n\thttpReq.Header.Set(\"anthropic-version\", \"2023-06-01\")\n\n\thttpResp, err := http.DefaultClient.Do(httpReq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"stream API request failed: %w\", err)\n\t}\n\tif httpResp.StatusCode != http.StatusOK {\n\t\tdefer httpResp.Body.Close()\n\t\trespBody, _ := io.ReadAll(httpResp.Body)\n\t\treturn nil, fmt.Errorf(\"stream API error (%s): %s\", httpResp.Status, string(respBody))\n\t}\n\treturn &streamReader{body: httpResp.Body, scanner: bufio.NewScanner(httpResp.Body)}, nil\n}\n\ntype streamReader struct {\n\tbody    io.ReadCloser\n\tscanner *bufio.Scanner\n\tclosed  bool\n}\n\nfunc (s *streamReader) Recv() (*ai.Response, error) {\n\tfor s.scanner.Scan() {\n\t\tline := strings.TrimSpace(s.scanner.Text())\n\t\tif line == \"\" || strings.HasPrefix(line, \":\") || strings.HasPrefix(line, \"event:\") {\n\t\t\tcontinue\n\t\t}\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/ai/anthropic/anthropic.go#L250-L286","documentation":"Returned by Provider.Stream when Anthropic responds with a non-200 status to a streaming request. The error message embeds the HTTP status line and the raw response body, which contains Anthropic's JSON error payload describing the actual failure (invalid API key, bad model name, rate limiting, etc.).","triggerScenarios":"Any non-200 from POST /v1/messages with stream=true: 401 invalid x-api-key, 400 invalid request (bad model, missing max_tokens), 429 rate limited, 5xx Anthropic outage.","commonSituations":"Expired or revoked ANTHROPIC_API_KEY, wrong API version header mismatch, requesting a model name that doesn't exist (e.g. typo in claude model ID), hitting org rate limits, or pointing BaseURL at a non-Anthropic-compatible proxy.","solutions":["Read the body embedded in the error message — it contains Anthropic's error.type and message pinpointing the cause.","Verify ANTHROPIC_API_KEY is valid: curl https://api.anthropic.com/v1/messages with the key and a minimal payload.","Confirm the model ID in the request matches an available Anthropic model and that max_tokens is set (required by the API).","Add retry with backoff for 429/5xx; check response headers retry-after for rate limits."],"exampleFix":"// before\nresp, err := provider.Stream(ctx, req)\nif err != nil { return err }\n// after\nresp, err := provider.Stream(ctx, req)\nif err != nil {\n\tvar httpErr *ai.HTTPError\n\tif errors.As(err, &httpErr) && httpErr.StatusCode == 429 {\n\t\ttime.Sleep(retryAfterBackoff(httpErr))\n\t\tresp, err = provider.Stream(ctx, req)\n\t}\n}","handlingStrategy":"retry","validationCode":"// before calling: validate key presence and model\nfunc validateOpts(key, model string) error {\n\tif key == \"\" { return errors.New(\"missing ANTHROPIC_API_KEY\") }\n\tif !strings.HasPrefix(model, \"claude-\") { return errors.New(\"suspicious model id: \"+model) }\n\treturn nil\n}","typeGuard":"func isAnthropicHTTPStatusErr(err error) (status string, body string, ok bool) {\n\tif err == nil { return };\n\tre := regexp.MustCompile(`stream API error \\((\\d{3}[^)]*)\\): (.*)`)\n\tm := re.FindStringSubmatch(err.Error())\n\tif m == nil { return }\n\treturn m[1], m[2], true\n}","tryCatchPattern":"resp, err := provider.Stream(ctx, req)\nif err != nil {\n\tif status, body, ok := isAnthropicHTTPStatusErr(err); ok {\n\t\tswitch {\n\t\tcase strings.HasPrefix(status, \"429\"), strings.HasPrefix(status, \"5\"):\n\t\t\t// retry with backoff\n\t\tcase strings.HasPrefix(status, \"4\"):\n\t\t\t// permanent: log body (contains Anthropic error.type), fail fast\n\t\t}\n\t}\n}","preventionTips":["Keep ANTHROPIC_API_KEY fresh and scoped","Pin a known-good anthropic-version","Implement exponential backoff for 429/5xx","Read the error body — Anthropic includes error.type and message"],"tags":["http","api-error","anthropic","streaming","status-code"],"backgroundTag":"anthropic-api-error-response","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}