{"record":{"id":"ceec2da9c062282a","repo":"gastownhall/beads","slug":"non-retryable-error-w","errorCode":null,"errorMessage":"non-retryable error: %w","messagePattern":"non-retryable error: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/compact/haiku.go","lineNumber":196,"sourceCode":"\t\t\t\tcontent := message.Content[0]\n\t\t\t\tif content.Type == \"text\" {\n\t\t\t\t\treturn content.Text, nil\n\t\t\t\t}\n\t\t\t\treturn \"\", fmt.Errorf(\"unexpected response format: not a text block (type=%s)\", content.Type)\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"unexpected response format: no content blocks\")\n\t\t}\n\n\t\tlastErr = err\n\n\t\tif ctx.Err() != nil {\n\t\t\treturn \"\", ctx.Err()\n\t\t}\n\n\t\tif !isRetryable(err) {\n\t\t\tspan.RecordError(err)\n\t\t\tspan.SetStatus(codes.Error, err.Error())\n\t\t\treturn \"\", fmt.Errorf(\"non-retryable error: %w\", err)\n\t\t}\n\t}\n\n\tif lastErr != nil {\n\t\tspan.RecordError(lastErr)\n\t\tspan.SetStatus(codes.Error, lastErr.Error())\n\t}\n\treturn \"\", fmt.Errorf(\"failed after %d retries: %w\", h.maxRetries+1, lastErr)\n}\n\nfunc isRetryable(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\tif errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n\t\treturn false\n\t}","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/compact/haiku.go#L178-L214","documentation":"callWithRetry distinguishes retryable failures (rate limits, transient network) from permanent ones. When isRetryable(err) is false — e.g. 401/403 auth, 400 bad request, invalid model — it records the error on the tracing span and returns immediately wrapped as \"non-retryable error\". Retrying the same call will fail identically.","triggerScenarios":"The Anthropic API (or transport) returns an error for which isRetryable returns false during callWithRetry: invalid API key, malformed request, unknown model name, permission denied, or a non-transient 4xx.","commonSituations":"Expired or revoked ANTHROPIC_API_KEY; wrong model identifier after a deprecation; oversized request body; MiniMax key used against the Anthropic endpoint (or vice versa) without the matching base URL.","solutions":["Read the wrapped cause — fix the actual 4xx problem it names (auth, model, request size)","Check that the key source matches the base URL: MINIMAX_API_KEY needs the MiniMax base URL, ANTHROPIC_API_KEY the Anthropic one","Regenerate/rotate the API key if the cause is 401/403","Fix the request: correct model ID, reduce payload size, validate fields"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// pre-flight: validate the key and model before calling\nif !strings.HasPrefix(apiKey, \"sk-ant-\") && os.Getenv(\"MINIMAX_API_KEY\") == \"\" {\n\treturn errors.New(\"suspicious API key format; check provider/base URL pairing\")\n}","typeGuard":"func isNonRetryable(err error) bool { return err != nil && strings.Contains(err.Error(), \"non-retryable error:\") }","tryCatchPattern":"summary, err := client.SummarizeTier1(ctx, issue)\nif isNonRetryable(err) {\n\t// do NOT retry; surface for human fix (key, model, request shape)\n\treturn fmt.Errorf(\"permanent AI failure, manual fix needed: %w\", err)\n}","preventionTips":["Match key provider to base URL (Anthropic key vs MiniMax key)","Use current, non-deprecated model IDs","Keep request payloads within model limits","Rotate keys before expiry"],"tags":["api","retry","anthropic","authentication"],"backgroundTag":"non-retryable-api-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}