grpc/grpc-go · error

rls: unregistered scheme in lookup_service

Error message

rls: unregistered scheme in lookup_service %s

What it means

Emitted from parseRLSProto (config.go:210) when the URI scheme parsed from lookup_service has no resolver registered via resolver.Get. The RLS balancer resolves the lookup_service address through the standard gRPC resolver machinery, so the scheme must correspond to a registered resolver (dns, passthrough, xds, unix, etc.).

Solutions

  1. Use the default scheme by omitting the scheme (the balancer prepends resolver.GetDefaultScheme()), or use a scheme you know is registered.
  2. Blank-import the resolver package providing the scheme, e.g. _ 'google.golang.org/grpc/resolver/dns' or _ 'google.golang.org/grpc/xds/internal/resolver'.
  3. Register your custom resolver with resolver.Register before dialing if you have a proprietary scheme.
  4. Switch to 'passthrough:///host:port' for tests or when the address is already resolved.

Example fix

// before
"lookupService": "mydns:///rls.example.com:443"  // mydns not registered

// after
"lookupService": "dns:///rls.example.com:443"
// and ensure the binary imports the dns resolver if required by your grpc version
Defensive patterns

Strategy: validation

Validate before calling

func validateLookupScheme(s string) error {
    u, err := url.Parse(s)
    if err != nil {
        u, err = url.Parse(resolver.GetDefaultScheme() + "/" + s)
        if err != nil {
            return fmt.Errorf("lookup_service %q is not a valid URI", s)
        }
    }
    scheme := u.Scheme
    if scheme == "" {
        scheme = resolver.GetDefaultScheme()
    }
    if resolver.Get(scheme) == nil {
        return fmt.Errorf("no resolver registered for scheme %q", scheme)
    }
    return nil
}

Type guard

func lookupSchemeIsRegistered(s string) bool {
    u, _ := url.Parse(s)
    scheme := u.Scheme
    if scheme == "" {
        scheme = resolver.GetDefaultScheme()
    }
    return resolver.Get(scheme) != nil
}

Prevention

When it happens

Trigger: lookupService uses a scheme like 'foo:///' where 'foo' was never registered with resolver.Register, or uses a scheme whose resolver lives in a package the binary does not import.

Common situations: Using 'dns:///host' but the binary does not import the dns resolver (in older grpc versions) ; using a custom resolver scheme without blank-importing its package; a typo in the scheme ('gdns', 'xds2'); relying on xds-federation/experimental schemes that are not built into this binary.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/75002af6c89c9413. Report an issue: GitHub.

Appendix: source

Thrown at balancer/rls/config.go:210

	// `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:
	// - if `max_age` > 5m, it should be set to 5 minutes
	//   only if stale age is not set
	// - if `stale_age` > `max_age`, ignore it
	// - if `stale_age` is set, then `max_age` must also be set
	maxAgeSet := false
	maxAge, err := convertDuration(rlsProto.GetMaxAge())
	if err != nil {

View on GitHub (pinned to 0c51461d27)