grafana/k6 · error
load must be called in the init context
Error message
load must be called in the init context
What it means
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.
Source
Thrown at internal/js/modules/k6/grpc/client.go:49
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
)
// Client represents a gRPC client that can be used to make RPC requests
type Client struct {
mds map[string]protoreflect.MethodDescriptor
conn *grpcext.Conn
vu modules.VU
addr string
types *protoregistry.Types
typesMtx sync.Mutex
}
// Load will parse the given proto files and make the file descriptors available to request.
func (c *Client) Load(importPaths []string, filenames ...string) ([]MethodInfo, error) {
if c.vu.State() != nil {
return nil, errors.New("load must be called in the init context")
}
initEnv := c.vu.InitEnv()
if initEnv == nil {
return nil, errors.New("missing init environment")
}
// If no import paths are specified, use the current working directory
if len(importPaths) == 0 {
importPaths = append(importPaths, initEnv.CWD.Path)
}
for i, s := range importPaths {
// Clean file scheme as it is the only supported scheme and the following APIs do not support them
importPaths[i] = strings.TrimPrefix(s, "file://")
}
resolver := protocompile.WithStandardImports(&protocompile.SourceResolver{View on GitHub (pinned to 93accf6570)
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
Example fix
// before
export default function () {
client.load([], 'protos/hello.proto');
client.invoke('/hello.HelloService/SayHello', {});
}
// after
client.load([], 'protos/hello.proto');
export default function () {
client.invoke('/hello.HelloService/SayHello', {});
} Defensive patterns
Strategy: validation
Validate before calling
// Static convention: client creation + load() live at module top level.
// If filenames come from config, resolve them in init:
import { options } from './config.js'; // plain module data, readable in init
client.load(options.protoImportPaths, ...options.protoFiles); Prevention
- 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
When it happens
Trigger: Calling client.load(importPaths, ...protoFiles) inside export default function(); calling it from setup()/teardown() or any helper invoked during iterations.
Common situations: 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.
Related errors
- couldn't unmarshal protoset file %s: %w
- can't convert method info: %w
- method %q not found in file descriptors
- open() can't be used with an empty filename
- k6/experimental/grpc has been graduated, please use k6/net/g
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/0f498bdfd4a46d42.
Report an issue: GitHub.