grpc/grpc-go · error

missing port after port-separator colon

Error message

missing port after port-separator colon

What it means

In delegatingresolver.parseTarget (delegatingresolver.go:234-246), after net.SplitHostPort succeeds, an empty port indicates the target had a trailing colon but no port number (e.g. "host:"). The function rejects this because the port is ambiguous. An empty host with a port (":80") is allowed and defaults to localhost.

Solutions

  1. Add the port number: "dns:///host:443".
  2. If you intend the default port (443 for the proxy path), simply omit the colon entirely: "dns:///host".
  3. Fix the config/template that produced the trailing colon.

Example fix

// before:
//   conn, err := grpc.Dial("dns:///host:", opts)

// after:
//   conn, err := grpc.Dial("dns:///host:443", opts)
Defensive patterns

Strategy: validation

Validate before calling

package main

import (
	"fmt"
	"strings"
)

func ensureHasPort(ep string) (string, error) {
	if strings.HasSuffix(ep, ":") {
		return "", fmt.Errorf("target %q ends with ':' but has no port", ep)
	}
	return ep, nil
}

// func main() { _, _ = ensureHasPort("host:") }

Try / catch

// Dial surfaces this synchronously from the delegating resolver.
//
//   conn, err := grpc.Dial(target, opts)
//   if err != nil && strings.Contains(err.Error(), "missing port after port-separator") {
//       return fmt.Errorf("fix trailing colon in %q", target)
//   }

Prevention

When it happens

Trigger: Triggered when the endpoint passed to parseTarget is of the form "host:" (trailing colon, no port digits), e.g. dial target "dns:///host:" under the proxy-default-port code path.

Common situations: A templating bug appends ':' to a hostname, a config field that should hold a port is empty producing "host:", or a copy-paste target omits the port number after the colon.

Related errors


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

Appendix: source

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

// An error is returned for empty targets or targets with a trailing colon
// but no port (e.g., "host:").
func parseTarget(target string) (string, error) {
	if target == "" {
		return "", fmt.Errorf("missing address")
	}

	host, port, err := net.SplitHostPort(target)
	if err != nil {
		// If SplitHostPort fails, it's likely because the port is missing.
		// We append the default port and return the result.
		return net.JoinHostPort(target, defaultPort), nil
	}

	// If SplitHostPort succeeds, we check for edge cases.
	if port == "" {
		// A success with an empty port means the target had a trailing colon,
		// e.g., "host:", which is an error.
		return "", fmt.Errorf("missing port after port-separator colon")
	}
	if host == "" {
		// A success with an empty host means the target was like ":80".
		// We default the host to "localhost".
		host = "localhost"
	}
	return net.JoinHostPort(host, port), nil
}

func skipProxy(address resolver.Address) bool {
	// Avoid proxy when network is not tcp.
	networkType, ok := networktype.Get(address)
	if !ok {
		networkType, _ = transport.ParseDialTarget(address.Addr)
	}
	if networkType != "tcp" {
		return true
	}

View on GitHub (pinned to 0c51461d27)