{"record":{"id":"41b8de12083701e5","repo":"grafana/k6","slug":"failed-to-parse-retryable-status-codes-w","errorCode":null,"errorMessage":"failed to parse retryable status codes: %w","messagePattern":"failed to parse retryable status codes: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cloudapi/insights/client.go","lineNumber":253,"sourceCode":"\n\tif cfg.AuthConfig.Enabled {\n\t\topts = append(opts, grpc.WithPerRPCCredentials(newPerRPCCredentials(cfg.AuthConfig)))\n\t}\n\n\trI, err := retryInterceptor(cfg.RetryConfig)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create retry interceptors: %w\", err)\n\t}\n\n\topts = append(opts, grpc.WithChainUnaryInterceptor([]grpc.UnaryClientInterceptor{rI}...))\n\n\treturn opts, nil\n}\n\nfunc retryInterceptor(retryConfig ClientRetryConfig) (grpc.UnaryClientInterceptor, error) {\n\trSC, err := retryableStatusCodes(retryConfig.RetryableStatusCodes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse retryable status codes: %w\", err)\n\t}\n\twithCodes := grpcRetry.WithCodes(rSC...)\n\twithMax := grpcRetry.WithMax(retryConfig.MaxAttempts)\n\twithPerRetryTimeout := grpcRetry.WithPerRetryTimeout(retryConfig.PerRetryTimeout)\n\tcallOptions := []grpcRetry.CallOption{withCodes, withMax, withPerRetryTimeout}\n\n\tbackoffConfig := retryConfig.BackoffConfig\n\tif backoffConfig.Enabled {\n\t\tbackoff := grpcRetry.WithBackoff(\n\t\t\tgrpcRetry.BackoffExponentialWithJitter(backoffConfig.WaitBetween, backoffConfig.JitterFraction))\n\t\tcallOptions = append(callOptions, backoff)\n\t}\n\n\tunaryInterceptor := grpcRetry.UnaryClientInterceptor(callOptions...)\n\treturn unaryInterceptor, nil\n}\n\nfunc retryableStatusCodes(retryableStatusCodes string) ([]codes.Code, error) {","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/cloudapi/insights/client.go#L235-L271","documentation":"retryableStatusCodes (internal/cloudapi/insights/client.go:271-286) splits the comma-separated RetryableStatusCodes string and parses each entry with codes.Code.UnmarshalJSON. An empty string fails with 'no retryable status codes provided'; an unrecognized name fails with 'invalid status code X provided'. Entries must be exact gRPC code names like 'Unavailable', 'DeadlineExceeded', 'ResourceExhausted'.","triggerScenarios":"Passing an empty retryable-codes value in the insights client config; a typo such as 'DeadLineExceeded', snake_case 'resource_exhausted', or stray whitespace after a comma; a list built by joining with the wrong separator.","commonSituations":"Hand-written env overrides in CI; configuration generated from YAML maps that mangle case; defaults overridden by an orchestration layer that assumes a different format.","solutions":["Use exact CamelCase gRPC code names, comma-separated with no extra whitespace: 'Unavailable,Internal,ResourceExhausted'","If you do not need custom retry codes, leave the field at its default rather than setting it to an empty string","Echo the final value right before k6 starts to catch CI interpolation artifacts"],"exampleFix":"// before (Go embedder)\ncfg.RetryConfig.RetryableStatusCodes = \"resource_exhausted\" // snake_case: invalid\n\n// after\ncfg.RetryConfig.RetryableStatusCodes = \"Unavailable,ResourceExhausted\"","handlingStrategy":"validation","validationCode":"// Go: validate the config string before constructing the insights client\nvar validCodes = map[string]bool{\"OK\": true, \"Canceled\": true, \"Unknown\": true, \"InvalidArgument\": true, \"DeadlineExceeded\": true, \"NotFound\": true, \"AlreadyExists\": true, \"PermissionDenied\": true, \"ResourceExhausted\": true, \"FailedPrecondition\": true, \"Aborted\": true, \"OutOfRange\": true, \"Unimplemented\": true, \"Internal\": true, \"Unavailable\": true, \"DataLoss\": true, \"Unauthenticated\": true}\n\nfunc retryCodesValid(s string) error {\n    if strings.TrimSpace(s) == \"\" {\n        return fmt.Errorf(\"retryable status codes must not be empty\")\n    }\n    for _, c := range strings.Split(s, \",\") {\n        if !validCodes[strings.TrimSpace(c)] {\n            return fmt.Errorf(\"invalid gRPC code %q\", c)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Copy code names from the gRPC codes documentation verbatim - case is significant","Add a unit test asserting your config template's retry codes parse","Treat 'failed to parse retryable status codes' as config, never as network - do not blind-retry"],"tags":["grpc","insights","retry","configuration","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}