grpc-ecosystem/grpc-gateway · error
HTTP rules without a matching selector: %s
Error message
HTTP rules without a matching selector: %s
What it means
protoc-gen-grpc-gateway's main collects HTTP rules that were declared on messages/services external to the files being generated (external HTTP rules) but were never bound to a matching RPC selector. If any remain unbound after LoadFromPlugin, generation aborts with 'HTTP rules without a matching selector'. This typically means an extended google.api.Http rule in an external proto references a method the gateway generator cannot see or that doesn't exist.
Source
Thrown at protoc-gen-grpc-gateway/main.go:96
if err := applyFlags(reg); err != nil {
return err
}
codegenerator.SetSupportedFeaturesOnPluginGen(gen)
generator := gengateway.New(reg, *useRequestContext, *registerFuncSuffix, *allowPatchFeature, *standalone, *useOpaqueAPI)
if grpclog.V(1) {
grpclog.Infof("Parsing code generator request")
}
if err := reg.LoadFromPlugin(gen); err != nil {
return err
}
unboundHTTPRules := reg.UnboundExternalHTTPRules()
if len(unboundHTTPRules) != 0 {
return fmt.Errorf("HTTP rules without a matching selector: %s", strings.Join(unboundHTTPRules, ", "))
}
targets := make([]*descriptor.File, 0, len(gen.Request.FileToGenerate))
for _, target := range gen.Request.FileToGenerate {
f, err := reg.LookupFile(target)
if err != nil {
return err
}
targets = append(targets, f)
}
files, err := generator.Generate(targets)
for _, f := range files {
if grpclog.V(1) {
grpclog.Infof("NewGeneratedFile %q in %s", f.GetName(), f.GoPkg)
}
genFile := gen.NewGeneratedFile(f.GetName(), protogen.GoImportPath(f.GoPkg.Path))View on GitHub (pinned to a58a4436a3)
Solutions
- Inspect the listed rule names in the error and verify each selector matches a fully-qualified RPC that exists and is included in the protoc invocation.
- Remove or fix stale google.api.http rules in external protos (selector typos, renamed methods).
- Include the file defining the targeted RPC in the same protoc run so the rule can bind.
- If the rule is intentionally unused, delete it rather than leaving it unbound.
Example fix
// before (in proto)
extend google.api.Http { get: "/v1/old" } = { selector: "svc.OldMethod" };
// after
extend google.api.Http { get: "/v1/thing" } = { selector: "svc.GetThing" }; // method exists and is generated Defensive patterns
Strategy: validation
Validate before calling
// Pre-check before invoking the plugin:
grep -rn 'selector:' api/ | while read -r line; do
svc=$(echo "$line" | sed -E 's/.*selector: *"([^"]+)".*/\1/')
grep -rq "rpc $(basename ${svc##*.})" api/ || echo "Unbound selector: $svc"
done Prevention
- Keep google.api.http selectors next to the RPCs they annotate and verify on rename.
- Run protoc over the full file set, never a partial subset.
- Add a CI lint that extracts selectors and checks they match existing RPCs.
When it happens
Trigger: Running protoc-gen-grpc-gateway when reg.UnboundExternalHTTPRules() returns non-empty — i.e. google.api.http annotations in files loaded via the plugin (LoadFromPlugin) whose selectors do not match any method in the files being generated or their registered dependencies.
Common situations: A third-party proto (or older google/api annotations.proto usage) carries http rules whose selector names a method that was renamed/deleted; generation invoked on only a subset of files so rules from a sibling file have no matching selector; typo in an http rule selector.
Related errors
- failed to lookup message type %s: %w
- JSON structure did not match request type
- unable to marshal non proto field
- unable to unmarshal non proto field
- empty MIME type
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/2f22e669c35aca12.
Report an issue: GitHub.