{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":52,"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":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/grafana/k6/blob/8d06114777191d21ef885d05c5f8aa9e1ed838ae/internal/js/modules/k6/grpc/client.go#L34-L70","documentation":"The gRPC Client.load(importPaths, ...filenames) parses .proto files into method descriptors so client.invoke() can marshal requests. Proto loading is expensive and the resulting descriptors must be identical for every VU, so load() only works during the init context: it refuses to run when c.vu.State() is non-nil, i.e. during setup/teardown/VU code.","triggerScenarios":"client.load(...) called inside export default function(), setup(), or any function invoked at runtime. Note connect/invoke are the opposite: they require a VU state and fail in init - load and invoke bracket the test lifecycle.","commonSituations":"Moving proto loading inside setup() to 'initialize once'; wrapping load in a helper that the default function calls; scripts converted from examples where load appears at the bottom of the file after exports (hoisting confusion in reorganized code).","solutions":["Move client.load(...) to the top level of the script, right after creating the client with new grpc.Client().","Keep connect() in the VU context (or use setup for a shared connection) but always load protos at init.","If protos are large, load them once at init and pass the client via the module scope - never re-load per iteration."],"exampleFix":"// before\nexport default function () {\n  client.load('./proto/', 'hello.proto'); // throws\n}\n\n// after\nconst client = new grpc.Client();\nclient.load('./proto/', 'hello.proto');\nexport default function () { client.connect(...); client.invoke(...); }","handlingStrategy":"validation","validationCode":"// proto loading is init-only: keep it next to the client construction\nconst client = new grpc.Client();\nclient.load(importPaths, ...protos); // top level only\nexport default function () { /* connect + invoke only */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember the lifecycle split: load at init, connect/invoke at runtime.","Keep one client per script, constructed and loaded at module scope."],"tags":["k6","grpc","proto","init-context","load"],"backgroundTag":"k6-init-context-violation","analyzedSha":"8d06114777191d21ef885d05c5f8aa9e1ed838ae","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}