grpc/grpc-go · warning

delegating_resolver: unable to build the proxy resolver

Error message

delegating_resolver: unable to build the proxy resolver: %v

What it means

After the target resolver returns TCP addresses that need a proxy, the delegating resolver lazily builds a DNS resolver for the proxy host via proxyURIResolver (delegatingresolver.go:417-430). Unlike construction errors for the target resolver, this failure is reported asynchronously through r.cc.ReportError rather than returned from New(), because it happens in a goroutine after New() has already returned.

Solutions

  1. Watch for errors via grpclog / the ClientConn error handler (this is delivered through ReportError, not returned from Dial).
  2. Verify the HTTPS_PROXY host component is a resolvable DNS name or IP.
  3. Simplify the proxy URL (scheme + host + port only) and remove exotic components.
  4. Temporarily disable the proxy (unset HTTPS_PROXY, add the target to NO_PROXY) to confirm the proxy is the source.

Example fix

# before
export HTTPS_PROXY='http:///path'   # malformed, no host

# after
export HTTPS_PROXY='http://proxy.corp:3128'
Defensive patterns

Strategy: validation

Validate before calling

package main

import (
	"fmt"
	"net/url"
	"os"
)

// Check the proxy URL host is a plausible DNS name / IP before the resolver
// tries to build a DNS resolver for it.
func validateProxyHost() error {
	raw := os.Getenv("HTTPS_PROXY")
	if raw == "" {
		return nil
	}
	u, err := url.Parse(raw)
	if err != nil {
		return fmt.Errorf("HTTPS_PROXY unparseable: %w", err)
	}
	if u.Host == "" {
		return fmt.Errorf("HTTPS_PROXY has no host")
	}
	return nil
}

// func main() { _ = validateProxyHost }

Try / catch

// This error is delivered asynchronously via the ClientConn error handler,
// NOT returned from Dial. Subscribe via grpc.WithReturnConnectionError or
// WaitForStateChange/GetState.
//
//   conn.WaitForStateChange(ctx, connectivity.Idle)
//   if state := conn.GetState(); state == connectivity.TransientFailure {
//       // proxy resolver build failed; check HTTPS_PROXY and logs.
//   }

Prevention

When it happens

Trigger: Triggered when r.proxyURIResolver(resolver.BuildOptions{}) returns an error inside the goroutine spawned by updateTargetResolverState. This is the "dns" scheme resolver failing to Build for the proxy host URL.

Common situations: The proxy URL host is empty or malformed in a way the DNS resolver builder rejects, the "dns" resolver was unregistered (would panic earlier), or BuildOptions are insufficient. Rare in practice because the proxy URL was already validated; usually a sign of an exotic proxy URL shape.

Related errors


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

Appendix: source

Thrown at internal/resolver/delegatingresolver/delegatingresolver.go:426

	// type, or are listed in the `NO_PROXY` environment variable, do not wait
	// for proxy update.
	if !needsProxyResolver(r.targetResolverState) {
		return r.cc.UpdateState(*r.targetResolverState)
	}

	// The proxy resolver may be rebuilt multiple times, specifically each time
	// the target resolver sends an update, even if the target resolver is built
	// successfully but building the proxy resolver fails.
	if len(r.proxyAddrs) == 0 {
		go func() {
			r.childMu.Lock()
			defer r.childMu.Unlock()
			if _, ok := r.proxyResolver.(nopResolver); !ok {
				return
			}
			proxyResolver, err := r.proxyURIResolver(resolver.BuildOptions{})
			if err != nil {
				r.cc.ReportError(fmt.Errorf("delegating_resolver: unable to build the proxy resolver: %v", err))
				return
			}
			r.proxyResolver = proxyResolver
		}()
	}

	err := r.updateClientConnStateLocked()
	if err != nil {
		go func() {
			r.childMu.Lock()
			defer r.childMu.Unlock()
			if r.proxyResolver != nil {
				r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
			}
		}()
	}
	return nil
}

View on GitHub (pinned to 0c51461d27)