{"record":{"id":"0f498bdfd4a46d42","repo":"grafana/k6","slug":"load-must-be-called-in-the-init-context","errorCode":null,"errorMessage":"load must be called in the init context","messagePattern":"load must be called in the init context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":49,"sourceCode":"\t\"google.golang.org/protobuf/types/descriptorpb\"\n\t\"google.golang.org/protobuf/types/dynamicpb\"\n)\n\n// Client represents a gRPC client that can be used to make RPC requests\ntype Client struct {\n\tmds  map[string]protoreflect.MethodDescriptor\n\tconn *grpcext.Conn\n\tvu   modules.VU\n\taddr string\n\n\ttypes    *protoregistry.Types\n\ttypesMtx sync.Mutex\n}\n\n// Load will parse the given proto files and make the file descriptors available to request.\nfunc (c *Client) Load(importPaths []string, filenames ...string) ([]MethodInfo, error) {\n\tif c.vu.State() != nil {\n\t\treturn nil, errors.New(\"load must be called in the init context\")\n\t}\n\n\tinitEnv := c.vu.InitEnv()\n\tif initEnv == nil {\n\t\treturn nil, errors.New(\"missing init environment\")\n\t}\n\n\t// If no import paths are specified, use the current working directory\n\tif len(importPaths) == 0 {\n\t\timportPaths = append(importPaths, initEnv.CWD.Path)\n\t}\n\n\tfor i, s := range importPaths {\n\t\t// Clean file scheme as it is the only supported scheme and the following APIs do not support them\n\t\timportPaths[i] = strings.TrimPrefix(s, \"file://\")\n\t}\n\n\tresolver := protocompile.WithStandardImports(&protocompile.SourceResolver{","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/client.go#L31-L67","documentation":"grpc.Client.Load parses .proto files and builds method descriptors, which is intentionally restricted to the init context: parsing protos per-iteration would be wasteful and the descriptors are shared VU-wide. The guard rejects the call whenever vu.State() is non-nil, i.e. whenever it runs inside a VU iteration.","triggerScenarios":"Calling client.load(importPaths, ...protoFiles) inside export default function(); calling it from setup()/teardown() or any helper invoked during iterations.","commonSituations":"Refactoring scripts so proto loading moves into a function called from the default function; attempting to load protos conditionally based on runtime state; code shared between init and VU phases.","solutions":["Move client.load() to the top level of the script, right after creating the client","Keep proto filenames static or derive them from exported options (available in init) rather than runtime state","If descriptors must vary, load all variants in init and select per iteration"],"exampleFix":"// before\nexport default function () {\n  client.load([], 'protos/hello.proto');\n  client.invoke('/hello.HelloService/SayHello', {});\n}\n\n// after\nclient.load([], 'protos/hello.proto');\nexport default function () {\n  client.invoke('/hello.HelloService/SayHello', {});\n}","handlingStrategy":"validation","validationCode":"// Static convention: client creation + load() live at module top level.\n// If filenames come from config, resolve them in init:\nimport { options } from './config.js'; // plain module data, readable in init\nclient.load(options.protoImportPaths, ...options.protoFiles);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the client/load block at the top of the file, immediately after imports","Never call load() from setup(), teardown(), or the default function","Need variant descriptors? Load all protos in init and branch per iteration"],"tags":["grpc","init-context","protobuf","load"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}