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
- Watch for errors via grpclog / the ClientConn error handler (this is delivered through ReportError, not returned from Dial).
- Verify the HTTPS_PROXY host component is a resolvable DNS name or IP.
- Simplify the proxy URL (scheme + host + port only) and remove exotic components.
- 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
- Keep HTTPS_PROXY as scheme://host:port only.
- Log connection-state transitions so async resolver errors are visible.
- Confirm the proxy host resolves (nslookup) before relying on it.
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
- delegating_resolver: invalid target address
- delegating_resolver: failed to determine proxy URL for…
- delegating_resolver: unable to build the resolver for target
- dns: error parsing A record IP address
- dns resolver: missing address
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)