vitessio/vitess · error

%s: unsupported URL host %s

Error message

%s: unsupported URL host %s

What it means

The gRPC resolver builder (build) maps the target URL's host to a discovery function; only hosts "vtctld" and "vtgate" are supported. Any other host means the resolver has no way to discover backend addresses, so building the resolver fails with this error. The logPrefix identifies which resolver/cluster failed.

Source

Thrown at go/vt/vtadmin/cluster/resolver/resolver.go:257

		return nil, err
	}

	b.m.Lock()
	b.resolvers = append(b.resolvers, r)
	b.m.Unlock()

	return r, nil
}

func (b *builder) build(target grpcresolver.Target, cc grpcresolver.ClientConn, opts grpcresolver.BuildOptions) (*resolver, error) {
	var fn func(context.Context, []string) ([]string, error)
	switch target.URL.Host {
	case "vtctld":
		fn = b.opts.Discovery.DiscoverVtctldAddrs
	case "vtgate":
		fn = b.opts.Discovery.DiscoverVTGateAddrs
	default:
		return nil, fmt.Errorf("%s: unsupported URL host %s", logPrefix, target.URL.Host)
	}

	var sc serviceconfig.Config
	if b.opts.BalancerPolicy != "" {
		// c.f. https://github.com/grpc/grpc/blob/master/doc/service_config.md#example
		scpr := cc.ParseServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{ "%s": {} }] }`, b.opts.BalancerPolicy))
		if scpr.Err != nil {
			return nil, fmt.Errorf("failed to initialize service config with load balancer policy %s: %s", b.opts.BalancerPolicy, scpr.Err)
		}

		sc = scpr.Config
	}

	ctx, cancel := context.WithCancel(context.Background())

	r := &resolver{
		component: target.URL.Host,
		// use the original cluster ID (not the sanitized scheme) for debugging/logging

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Set the target URL host to exactly 'vtctld' or 'vtgate' (e.g. cluster://mycluster/vtctld).
  2. Check how the dial target string is constructed/templated for typos.
  3. Verify scheme parsing: the host is whatever sits between the second '/' and the next '/'.
  4. If a new component is required, extend build's switch with a discovery function (code change).

Example fix

// before
cc, err := resolver.Build(target, ...) // target: "cluster://vtctlds/account"
// after
cc, err := resolver.Build("cluster://account/vtctld", ...)
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(target)
if err != nil { return err }
if h := u.Host; h != "vtctld" && h != "vtgate" {
  return fmt.Errorf("unsupported resolver host %q", h)
}

Type guard

func isSupportedResolverTarget(target string) bool {
  u, err := url.Parse(target)
  return err == nil && (u.Host == "vtctld" || u.Host == "vtgate")
}

Try / catch

cc, err := resolver.Build(target, opts)
if err != nil {
  if strings.Contains(err.Error(), "unsupported URL host") {
    log.Warn("bad dial target, falling back", slog.String("target", target))
    return grpc.Dial(fallbackAddr)
  }
  return err
}

Prevention

When it happens

Trigger: Calling resolver.Build or mustBuild with a target like "cluster://ks/other" or a mistyped scheme URL whose URL.Host is not exactly 'vtctld' or 'vtgate' — e.g. "cluster://vtctlds/..." (plural) or a full hostname in the target.

Common situations: Constructing gRPC dial targets by hand with wrong pseudo-host names; templated configs where the component segment is interpolated with a typo; adding a new component type without updating the resolver switch.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/30af6b923f1eab52. Report an issue: GitHub.