grpc/grpc-go · error
rls: invalid target URI in lookup_service
Error message
rls: invalid target URI in lookup_service %s
What it means
Emitted from parseRLSProto (config.go:203) when the lookup_service string cannot be parsed as a URI even after the balancer retries by prepending the default resolver scheme ('<default>:///' + lookupService). This is the fallback path; it fires when url.Parse rejects the string in both forms, i.e. the string contains control characters or other bytes that make it an invalid URI.
Solutions
- Strip whitespace/newlines from the lookup_service value and re-test with url.Parse locally.
- Provide a fully-qualified URI with an explicit scheme and authority, e.g. 'dns:///rls.example.com:443'.
- Percent-encode any characters that are legal in a hostname but not in a URI path/query if they appear.
Example fix
// before "lookupService": "rls.example.com:443 " // after "lookupService": "dns:///rls.example.com:443"
Defensive patterns
Strategy: validation
Validate before calling
func parseLookupServiceURI(s string) (*url.URL, error) {
u, err := url.Parse(s)
if err != nil {
u, err = url.Parse(resolver.GetDefaultScheme() + "/" + s)
if err != nil {
return nil, fmt.Errorf("lookup_service %q is not a valid URI: %w", s, err)
}
}
return u, nil
} Type guard
func isParsableLookupURI(s string) bool {
_, err := parseLookupServiceURI(s)
return err == nil
} Prevention
- Use fully-qualified URIs ('dns:///host:port') to avoid scheme-prepending ambiguity.
- Trim whitespace from operator-supplied hostnames before templating.
- Reject lookup_service values containing spaces or control bytes at config ingest.
When it happens
Trigger: lookupService contains characters that url.Parse refuses (e.g. raw spaces, control bytes, or malformed percent-encoding) in both the bare form and the scheme-prepended form.
Common situations: A copy-paste of a hostname with a stray space or newline; percent-encoding errors; a value that is actually a full connection string with extra metadata rather than a host:port / URI.
Related errors
- rls: empty lookup_service in route lookup config %+v
- rls: unregistered scheme in lookup_service
- rls: bad control channel service config
- rls: bad RouteLookupConfig proto %+v
- rls: cache_size_bytes must be set to a non-zero value: %+v
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/678ff82cd0fc3db1.
Report an issue: GitHub.
Appendix: source
Thrown at balancer/rls/config.go:203
func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {
// Validations specified on the `grpc_keybuilders` field are performed here.
kbMap, err := keys.MakeBuilderMap(rlsProto)
if err != nil {
return nil, err
}
// `lookup_service` field must be set and must parse as a target URI.
lookupService := rlsProto.GetLookupService()
if lookupService == "" {
return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
}
parsedTarget, err := url.Parse(lookupService)
if err != nil {
// url.Parse() fails if scheme is missing. Retry with default scheme.
parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
if err != nil {
return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
}
}
if parsedTarget.Scheme == "" {
parsedTarget.Scheme = resolver.GetDefaultScheme()
}
if resolver.Get(parsedTarget.Scheme) == nil {
return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
}
lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
if err != nil {
return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
}
if lookupServiceTimeout == 0 {
lookupServiceTimeout = defaultLookupServiceTimeout
}
// Validations performed here:View on GitHub (pinned to 0c51461d27)