{"record":{"id":"aa36b556e874e511","repo":"grpc/grpc-go","slug":"missing-port-after-port-separator-colon","errorCode":null,"errorMessage":"missing port after port-separator colon","messagePattern":"missing port after port-separator colon","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/resolver/delegatingresolver/delegatingresolver.go","lineNumber":245,"sourceCode":"// An error is returned for empty targets or targets with a trailing colon\n// but no port (e.g., \"host:\").\nfunc parseTarget(target string) (string, error) {\n\tif target == \"\" {\n\t\treturn \"\", fmt.Errorf(\"missing address\")\n\t}\n\n\thost, port, err := net.SplitHostPort(target)\n\tif err != nil {\n\t\t// If SplitHostPort fails, it's likely because the port is missing.\n\t\t// We append the default port and return the result.\n\t\treturn net.JoinHostPort(target, defaultPort), nil\n\t}\n\n\t// If SplitHostPort succeeds, we check for edge cases.\n\tif port == \"\" {\n\t\t// A success with an empty port means the target had a trailing colon,\n\t\t// e.g., \"host:\", which is an error.\n\t\treturn \"\", fmt.Errorf(\"missing port after port-separator colon\")\n\t}\n\tif host == \"\" {\n\t\t// A success with an empty host means the target was like \":80\".\n\t\t// We default the host to \"localhost\".\n\t\thost = \"localhost\"\n\t}\n\treturn net.JoinHostPort(host, port), nil\n}\n\nfunc skipProxy(address resolver.Address) bool {\n\t// Avoid proxy when network is not tcp.\n\tnetworkType, ok := networktype.Get(address)\n\tif !ok {\n\t\tnetworkType, _ = transport.ParseDialTarget(address.Addr)\n\t}\n\tif networkType != \"tcp\" {\n\t\treturn true\n\t}","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/resolver/delegatingresolver/delegatingresolver.go#L227-L263","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add the port number: \"dns:///host:443\".","If you intend the default port (443 for the proxy path), simply omit the colon entirely: \"dns:///host\".","Fix the config/template that produced the trailing colon."],"exampleFix":"// before:\n//   conn, err := grpc.Dial(\"dns:///host:\", opts)\n\n// after:\n//   conn, err := grpc.Dial(\"dns:///host:443\", opts)","handlingStrategy":"validation","validationCode":"package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nfunc ensureHasPort(ep string) (string, error) {\n\tif strings.HasSuffix(ep, \":\") {\n\t\treturn \"\", fmt.Errorf(\"target %q ends with ':' but has no port\", ep)\n\t}\n\treturn ep, nil\n}\n\n// func main() { _, _ = ensureHasPort(\"host:\") }","typeGuard":null,"tryCatchPattern":"// Dial surfaces this synchronously from the delegating resolver.\n//\n//   conn, err := grpc.Dial(target, opts)\n//   if err != nil && strings.Contains(err.Error(), \"missing port after port-separator\") {\n//       return fmt.Errorf(\"fix trailing colon in %q\", target)\n//   }","preventionTips":["Avoid templating that appends ':' unconditionally to hostnames.","When a port is optional, omit the colon entirely rather than leaving it empty.","Validate dial targets with net.SplitHostPort before dialing."],"tags":["resolver","delegating-resolver","addressing","validation","grpc"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}