{"record":{"id":"20d725d2bbdb457f","repo":"grpc-ecosystem/grpc-gateway","slug":"exceeded-recursive-count-d-for-query-parameter","errorCode":null,"errorMessage":"exceeded recursive count (%d) for query parameter %q","messagePattern":"exceeded recursive count \\((.+?)\\) for query parameter %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"protoc-gen-openapiv2/internal/genopenapi/template.go","lineNumber":454,"sourceCode":"\t\t\t\t}\n\t\t\t}\n\t\t\tvalueComments := enumValueProtoComments(reg, enum)\n\t\t\tif valueComments != \"\" {\n\t\t\t\tparam.Description = strings.TrimLeft(param.Description+\"\\n\\n \"+valueComments, \"\\n\")\n\t\t\t}\n\t\t}\n\t\treturn []openapiParameterObject{param}, nil\n\t}\n\n\t// nested type, recurse\n\tmsg, err := reg.LookupMsg(\"\", fieldType)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unknown message type %s\", fieldType)\n\t}\n\n\t// Check for cyclical message reference:\n\tif ok := cycle.Check(*msg.Name); !ok {\n\t\treturn nil, fmt.Errorf(\"exceeded recursive count (%d) for query parameter %q\", cycle.count, fieldType)\n\t}\n\n\t// Construct a new map with the message name so a cycle further down the recursive path can be detected.\n\t// Do not keep anything in the original touched reference and do not pass that reference along.  This will\n\t// prevent clobbering adjacent records while recursing.\n\ttouchedOut := cycle.Branch()\n\n\tfor _, nestedField := range msg.Fields {\n\t\tif !isVisible(getFieldVisibilityOption(nestedField), reg) {\n\t\t\tcontinue\n\t\t}\n\n\t\tfieldName := reg.FieldName(field)\n\t\tp, err := nestedQueryParams(msg, nestedField, prefix+fieldName+\".\", reg, pathParams, body, touchedOut)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparams = append(params, p...)","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/grpc-ecosystem/grpc-gateway/blob/a58a4436a376a4bcc7d8f10c4d4f919a8438bba9/protoc-gen-openapiv2/internal/genopenapi/template.go#L436-L472","documentation":"nestedQueryParams protects against infinitely recursive query parameters (a message containing itself, directly or transitively) using a cycle tracker. When cycle.Check(msg name) fails — the message already appeared in the current recursion path beyond the allowed count — generation stops with 'exceeded recursive count (%d) for query parameter %q'. OpenAPI query parameters cannot be rendered for cyclic structures.","triggerScenarios":"A GET request proto has a nested message query field that (transitively) references itself — e.g. message A { A child; } or A -> B -> A — and the recursion depth exceeds the cycle count limit during nestedQueryParams recursion.","commonSituations":"Tree-like protos (parent/child, node graphs) used as GET query request types; reusing a generic recursive envelope message in query requests; accidental self-reference added during refactoring.","solutions":["Remove the recursive field from the query-path of the request message, or break the cycle with a wrapper message that stops recursion.","Restructure so recursive/nested data is sent in the body (POST) rather than as query parameters.","Use a depth-limited concrete type instead of a self-referential one for query fields.","If the recursion is intentional but deep, flatten the fields the client should pass as query params."],"exampleFix":"// before\nmessage Node { string id = 1; Node child = 2; }\nrpc Get(GetRequest) returns ...; // GetRequest uses Node in query\n\n// after\nmessage Node { string id = 1; } // move recursive child out of query type or into body","handlingStrategy":"validation","validationCode":"// Static check: refuse self-referential messages in GET query requests\nfunc hasCycle(m *Message, seen map[string]bool) bool {\n    if seen[m.Name] { return true }\n    seen[m.Name] = true\n    for _, f := range m.Fields {\n        if f.IsMessage && hasCycle(f.MessageType, seen) { return true }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never use recursive message types as GET query request fields.","Send recursive/tree data via request bodies (POST) instead.","Add proto lint rules (buf) rejecting self-referential query types."],"tags":["codegen","openapi","recursion","cycle-detection"],"backgroundTag":"recursive-type-overflow","analyzedSha":"a58a4436a376a4bcc7d8f10c4d4f919a8438bba9","analyzedAt":"2026-09-02T10:28:31.537Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}