grpc/grpc-go · error

passthrough: received empty target in Build()

Error message

passthrough: received empty target in Build()

What it means

Returned by passthroughBuilder.Build (internal/resolver/passthrough/passthrough.go:35) when target.Endpoint() is empty AND opts.Dialer is nil. The passthrough resolver hands the endpoint straight to gRPC as the address; with no endpoint and no custom dialer there is nothing to dial, so Build fails. Note the condition: a custom dialer makes an empty endpoint acceptable (the dialer decides where to connect).

Source

Thrown at internal/resolver/passthrough/passthrough.go:35

 */

// Package passthrough implements a pass-through resolver. It sends the target
// name without scheme back to gRPC as resolved address.
package passthrough

import (
	"errors"

	"google.golang.org/grpc/resolver"
)

const scheme = "passthrough"

type passthroughBuilder struct{}

func (*passthroughBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	if target.Endpoint() == "" && opts.Dialer == nil {
		return nil, errors.New("passthrough: received empty target in Build()")
	}
	r := &passthroughResolver{
		target: target,
		cc:     cc,
	}
	r.start()
	return r, nil
}

func (*passthroughBuilder) Scheme() string {
	return scheme
}

type passthroughResolver struct {
	target resolver.Target
	cc     resolver.ClientConn
}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Pass a non-empty target to grpc.Dial, e.g. grpc.Dial("passthrough:///backend.example.com:443").
  2. If you intentionally dial with an empty endpoint, supply a custom dialer via grpc.WithContextDialer (Build allows empty endpoint when opts.Dialer != nil).
  3. Validate the address string is non-empty before Dialing.

Example fix

// before
conn, _ := grpc.Dial("passthrough:///", ...) // err: received empty target

// after
addr := os.Getenv("BACKEND_ADDR")
if addr == "" { log.Fatal("BACKEND_ADDR not set") }
conn, err := grpc.Dial("passthrough:///"+addr, ...)

// alternate: custom dialer with empty endpoint
// conn, _ := grpc.Dial("passthrough:///", grpc.WithContextDialer(myDialer))
Defensive patterns

Strategy: validation

Validate before calling

endpoint := strings.TrimSpace(rawTarget)
if endpoint == "" && customDialer == nil {
    return errors.New("dial target is empty; provide an address or a custom dialer")
}
dialTarget := "passthrough:///" + endpoint

Try / catch

conn, err := grpc.Dial(target, ...)
if err != nil {
    if strings.Contains(err.Error(), "passthrough: received empty target") {
        log.Fatal("dial target empty; set BACKEND_ADDR or pass a dialer")
    }
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Dialing with the passthrough scheme (or default scheme) where the target string has no endpoint, e.g. grpc.Dial("passthrough:///") or grpc.Dial("") without a WithContextDialer option. The resolver Build() runs during Dial and returns this error.

Common situations: Empty target string passed to grpc.Dial; building a target programmatically and the host portion is empty; using the default resolver (which falls back to passthrough) with a malformed address; expecting an env var to fill the address but it is unset.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/6c83f8a942fc5075. Report an issue: GitHub.