{"record":{"id":"b5ea03461cffa8d1","repo":"vitessio/vitess","slug":"expected-either-pointer-for-unary-or-named-inter","errorCode":null,"errorMessage":"expected either pointer (for unary) or named interface (for streaming) rpc result type, got %T","messagePattern":"expected either pointer \\(for unary\\) or named interface \\(for streaming\\) rpc result type, got %T","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtctl/vtctldclient/codegen/main.go","lineNumber":168,"sourceCode":"\t\t\t\tvar argImports []typeArgImport\n\t\t\t\tlocalType, localImport, pkgPath, argImports, err = extractLocalNamedType(result)\n\t\t\t\tif err == nil {\n\t\t\t\t\tfor _, imp := range argImports {\n\t\t\t\t\t\timportNames = addImport(imp.localImport, imp.pkgPath, importNames, imports)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err == nil && *local {\n\t\t\t\t\t// We need to get the pointer type returned by `stream.Recv()`\n\t\t\t\t\t// in the local case for the stream adapter.\n\t\t\t\t\tvar recvType, recvImport, recvPkgPath string\n\t\t\t\t\trecvType, recvImport, recvPkgPath, err = extractRecvType(result)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tf.StreamMessage = buildParam(\"stream\", recvImport, recvType, true)\n\t\t\t\t\t\timportNames = addImport(recvImport, recvPkgPath, importNames, imports)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\terr = fmt.Errorf(\"expected either pointer (for unary) or named interface (for streaming) rpc result type, got %T\", result.Type().Underlying())\n\t\t\t}\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"expected either pointer (for unary) or named interface (for streaming) rpc result type, got %T\", result.Type())\n\t\t}\n\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tf.Result = buildParam(result.Name(), localImport, localType, !f.IsStreaming)\n\t\timportNames = addImport(localImport, pkgPath, importNames, imports)\n\t}\n\n\tsort.Strings(importNames)\n\tsort.Strings(funcNames)\n\n\tdef := &ClientInterfaceDef{\n\t\tPackageName: *pkgName,","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtctl/vtctldclient/codegen/main.go#L150-L186","documentation":"The codegen walks each RPC method's result signature and only accepts a pointer type (unary reply, e.g. *vtctlservicepb.XxxResponse) or a named interface (streaming, e.g. VtctldServer_XxxStream). When the default case of the underlying-type switch hits an unexpected underlying shape, it panics with this message including %T of the offending type. It guards against generating invalid client stubs for a malformed service definition.","triggerScenarios":"Running codegen over a proto-generated service interface where a method's second return value is neither a pointer-to-struct nor a named interface — e.g. a plain value struct, a slice/map, or an unnamed interface type in the underlying switch's default branch.","commonSituations":"Hand-edited or regenerated proto service with a new method whose signature doesn't match the expected patterns; proto plugin version producing different return types; adding an RPC without the standard (resp *XxxResponse, err error) shape.","solutions":["Fix the proto service method so it returns (*vtctlservicepb.XxxResponse, error) for unary or a named stream interface for server-streaming RPCs","Regenerate the proto bindings (make proto / protoc) rather than hand-editing the generated Go interface","If it's a new method pattern, update the codegen switch in main.go to support the new shape","Confirm the proto file's rpc is properly declared streaming vs unary"],"exampleFix":"// before\nGetKeyspace(ctx) (Keyspace, error)\n// after\nGetKeyspace(ctx) (*vtctlservicepb.GetKeyspaceResponse, error)","handlingStrategy":"validation","validationCode":"// pre-check each method's result type shape before codegen\nresults := fn.Type().(type with 2 results)\nif !isPointerOrNamedInterface(results[1].Type()) {\n  return fmt.Errorf(\"bad rpc result type %s\", results[1].Type())\n}","typeGuard":"func validRPCResult(t types.Type) bool {\n  switch u := t.Underlying().(type) {\n  case *types.Pointer:\n    return true\n  case *types.Interface:\n    named, ok := t.(*types.Named)\n    return ok && u.NumMethods() > 0 && named != nil\n  }\n  return false\n}","tryCatchPattern":"err := buildMethod(m)\nif err != nil {\n  panic(fmt.Errorf(\"method %s: %w\", m.Name(), err)) // fail fast at codegen time\n}","preventionTips":["Never hand-edit proto-generated service interfaces","Declare new RPCs with the standard (resp *XResponse, err) or named-stream shape","Regenerate protos with make proto after .proto edits"],"tags":["go","codegen","grpc","proto","type-mismatch"],"backgroundTag":"unexpected-rpc-signature","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}