grafana/k6 · error

failed to parse retryable status codes: %w

Error message

failed to parse retryable status codes: %w

What it means

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'.

Source

Thrown at internal/cloudapi/insights/client.go:253

	if cfg.AuthConfig.Enabled {
		opts = append(opts, grpc.WithPerRPCCredentials(newPerRPCCredentials(cfg.AuthConfig)))
	}

	rI, err := retryInterceptor(cfg.RetryConfig)
	if err != nil {
		return nil, fmt.Errorf("failed to create retry interceptors: %w", err)
	}

	opts = append(opts, grpc.WithChainUnaryInterceptor([]grpc.UnaryClientInterceptor{rI}...))

	return opts, nil
}

func retryInterceptor(retryConfig ClientRetryConfig) (grpc.UnaryClientInterceptor, error) {
	rSC, err := retryableStatusCodes(retryConfig.RetryableStatusCodes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse retryable status codes: %w", err)
	}
	withCodes := grpcRetry.WithCodes(rSC...)
	withMax := grpcRetry.WithMax(retryConfig.MaxAttempts)
	withPerRetryTimeout := grpcRetry.WithPerRetryTimeout(retryConfig.PerRetryTimeout)
	callOptions := []grpcRetry.CallOption{withCodes, withMax, withPerRetryTimeout}

	backoffConfig := retryConfig.BackoffConfig
	if backoffConfig.Enabled {
		backoff := grpcRetry.WithBackoff(
			grpcRetry.BackoffExponentialWithJitter(backoffConfig.WaitBetween, backoffConfig.JitterFraction))
		callOptions = append(callOptions, backoff)
	}

	unaryInterceptor := grpcRetry.UnaryClientInterceptor(callOptions...)
	return unaryInterceptor, nil
}

func retryableStatusCodes(retryableStatusCodes string) ([]codes.Code, error) {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Use exact CamelCase gRPC code names, comma-separated with no extra whitespace: 'Unavailable,Internal,ResourceExhausted'
  2. If you do not need custom retry codes, leave the field at its default rather than setting it to an empty string
  3. Echo the final value right before k6 starts to catch CI interpolation artifacts

Example fix

// before (Go embedder)
cfg.RetryConfig.RetryableStatusCodes = "resource_exhausted" // snake_case: invalid

// after
cfg.RetryConfig.RetryableStatusCodes = "Unavailable,ResourceExhausted"
Defensive patterns

Strategy: validation

Validate before calling

// Go: validate the config string before constructing the insights client
var 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}

func retryCodesValid(s string) error {
    if strings.TrimSpace(s) == "" {
        return fmt.Errorf("retryable status codes must not be empty")
    }
    for _, c := range strings.Split(s, ",") {
        if !validCodes[strings.TrimSpace(c)] {
            return fmt.Errorf("invalid gRPC code %q", c)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/41b8de12083701e5. Report an issue: GitHub.