grafana/k6 · error
couldn't unmarshal protoset file %s: %w
Error message
couldn't unmarshal protoset file %s: %w
What it means
grpc.Client.load() read the file bytes but proto.Unmarshal could not decode them as a protobuf-serialized FileDescriptorSet; the error is wrapped as 'couldn't unmarshal protoset file <path>' (internal/js/modules/k6/grpc/client.go:118). The file exists and is readable - its bytes are simply not a protoset. The offending path is included in the message.
Source
Thrown at internal/js/modules/k6/grpc/client.go:118
if initEnv == nil {
return nil, errors.New("missing init environment")
}
absFilePath := initEnv.GetAbsFilePath(protosetPath)
fdsetFile, err := initEnv.FileSystems["file"].Open(absFilePath)
if err != nil {
return nil, fmt.Errorf("couldn't open protoset: %w", err)
}
defer func() { _ = fdsetFile.Close() }()
fdsetBytes, err := io.ReadAll(fdsetFile)
if err != nil {
return nil, fmt.Errorf("couldn't read protoset: %w", err)
}
fdset := &descriptorpb.FileDescriptorSet{}
if err = proto.Unmarshal(fdsetBytes, fdset); err != nil {
return nil, fmt.Errorf("couldn't unmarshal protoset file %s: %w", protosetPath, err)
}
return c.convertToMethodInfo(fdset)
}
// Note: this function was lifted from `lib/options.go`
func decryptPrivateKey(key, password []byte) ([]byte, error) {
block, _ := pem.Decode(key)
if block == nil {
return nil, errors.New("failed to decode PEM key")
}
blockType := block.Type
if blockType == "ENCRYPTED PRIVATE KEY" {
return nil, errors.New("encrypted pkcs8 formatted key is not supported")
}
/*
Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is stillView on GitHub (pinned to 93accf6570)
Solutions
- Regenerate correctly: protoc --include_imports --descriptor_set_out=service.protoset service.proto
- Sanity-check the file: `file service.protoset` should report data, not ASCII text
- Or skip the protoset and use server reflection: client.connect(addr, { reflect: true })
Example fix
# before (produces a .proto text file, not a protoset) protoc --proto_path=. service.proto --descriptor_set_out=service.protoset # after (binary FileDescriptorSet including imports) protoc --include_imports --proto_path=. service.proto --descriptor_set_out=service.protoset
Defensive patterns
Strategy: validation
Validate before calling
// cheap guard: a valid protoset is binary protobuf, not .proto text
const head = open('./pb/service.protoset');
if (head.startsWith('syntax') || head.startsWith('//')) {
throw new Error('file looks like .proto source; compile with protoc --descriptor_set_out first');
}
client.load('./pb/service.protoset'); Try / catch
try {
client.load(p);
} catch (e) {
if (/couldn't unmarshal protoset/.test(e.message)) {
throw new Error(`${p} is not a FileDescriptorSet; regenerate with protoc --include_imports --descriptor_set_out`);
}
throw e;
} Prevention
- Always compile with --descriptor_set_out (plus --include_imports)
- Never rename .proto files into .protoset
- Treat protosets as binary in git/CI (no text-mode transfers)
When it happens
Trigger: Passing a .proto source file (plain text) to client.load(); passing a JSON descriptor or a single FileDescriptorProto instead of a set; a protoset corrupted by text-mode transfer (base64/UTF-8 mangling, git media filters).
Common situations: Generating descriptors with the wrong protoc invocation (must be --descriptor_set_out, typically with --include_imports); renaming a .proto file to .protoset instead of compiling; binary files mangled through chat tools or copy-paste.
Related errors
- couldn't open protoset: %w
- couldn't read protoset: %w
- can't convert method info: %w
- method %q not found in file descriptors
- unable to serialise request object: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/0f9e2d4da1c19191.
Report an issue: GitHub.