{"record":{"id":"8972a6a89b56a745","repo":"grpc/grpc-go","slug":"pemfile-protojson-unmarshal-v-failed-v","errorCode":null,"errorMessage":"pemfile: protojson.Unmarshal(%+v) failed: %v","messagePattern":"pemfile: protojson\\.Unmarshal\\(%\\+v\\) failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/tls/certprovider/pemfile/builder.go","lineNumber":94,"sourceCode":"\tif !envconfig.XDSSPIFFEEnabled {\n\t\tcfg.SPIFFETrustBundleMapFile = \"\"\n\t}\n\n\topts := Options{\n\t\tCertFile:            cfg.CertificateFile,\n\t\tKeyFile:             cfg.PrivateKeyFile,\n\t\tRootFile:            cfg.CACertificateFile,\n\t\tSPIFFEBundleMapFile: cfg.SPIFFETrustBundleMapFile,\n\t\t// Refresh interval is the only field in the configuration for which we\n\t\t// support a default value. We cannot possibly have valid defaults for\n\t\t// file paths to watch. Also, it is valid to specify an empty path for\n\t\t// some of those fields if the user does not want to watch them.\n\t\tRefreshDuration: defaultRefreshInterval,\n\t}\n\tif cfg.RefreshInterval != nil {\n\t\tdur := &durationpb.Duration{}\n\t\tif err := protojson.Unmarshal(cfg.RefreshInterval, dur); err != nil {\n\t\t\treturn Options{}, fmt.Errorf(\"pemfile: protojson.Unmarshal(%+v) failed: %v\", cfg.RefreshInterval, err)\n\t\t}\n\t\topts.RefreshDuration = dur.AsDuration()\n\t}\n\n\tif err := opts.validate(); err != nil {\n\t\treturn Options{}, err\n\t}\n\treturn opts, nil\n}\n","sourceCodeStart":76,"sourceCodeEnd":104,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/tls/certprovider/pemfile/builder.go#L76-L104","documentation":"The pemfile plugin parsed the top-level JSON successfully but failed to decode the refresh_interval field as a protobuf Duration (via protojson.Unmarshal into durationpb.Duration). The field is optional, so this only fires when refresh_interval is present yet not a valid proto-JSON Duration representation.","triggerScenarios":"Supplying refresh_interval as a JSON string that is not a valid duration (e.g. \"10\" or \"1h30m\"), as a numeric value (protojson expects a string like \"10s\"), or as an object that does not match the well-known Duration schema (\"seconds\"/\"nanos\").","commonSituations":"Mixing Go time.Duration string syntax (\"1h30m\") with proto-JSON Duration syntax (\"3600s\"/\"3600.5s\"); pasting a number instead of a quoted string; field left over from a config meant for a different runtime.","solutions":["Format refresh_interval as a quoted proto-JSON Duration string such as \"600s\" or \"10.5s\" (seconds-based, fractional seconds allowed).","Drop the refresh_interval field entirely to fall back to the 10-minute default (defaultRefreshInterval).","Validate with protoc or the google.protobuf.Duration JSON schema before deployment."],"exampleFix":"// before\n{ \"ca_certificate_file\": \"ca.crt\", \"refresh_interval\": 600 }\n\n// after\n{ \"ca_certificate_file\": \"ca.crt\", \"refresh_interval\": \"600s\" }","handlingStrategy":"validation","validationCode":"import \"google.golang.org/protobuf/types/known/durationpb\"\nimport \"google.golang.org/protobuf/encoding/protojson\"\n\nfunc validRefreshInterval(b []byte) error {\n    if len(b) == 0 { return nil }\n    d := &durationpb.Duration{}\n    return protojson.Unmarshal(b, d)\n}","typeGuard":null,"tryCatchPattern":"opts, err := pemfile.BuildableConfigFromJSON(raw) // or ParseConfig\nif err != nil { return fmt.Errorf(\"pemfile config: %w\", err) }","preventionTips":["Format durations as proto-JSON strings like \"600s\", never Go-style \"10m\" or bare numbers.","Omit refresh_interval to accept the 10-minute default.","Document the expected format next to the config field in your template."],"tags":["grpc","config","pemfile","proto","duration","certprovider"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}