{"record":{"id":"edb055eb5a5134a2","repo":"grpc/grpc-go","slug":"provider-q-q-build-v-failed","errorCode":null,"errorMessage":"provider(%q, %q).Build(%v) failed","messagePattern":"provider\\(%q, %q\\)\\.Build\\((.+?)\\) failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/tls/certprovider/store.go","lineNumber":160,"sourceCode":"// invocations of this method with the same opts will result in provider\n// instances being reused.\nfunc (bc *BuildableConfig) Build(opts BuildOptions) (Provider, error) {\n\tprovStore.mu.Lock()\n\tdefer provStore.mu.Unlock()\n\n\tsk := storeKey{\n\t\tname:   bc.name,\n\t\tconfig: string(bc.config),\n\t\topts:   opts,\n\t}\n\tif wp, ok := provStore.providers[sk]; ok {\n\t\twp.refCount++\n\t\treturn newSingleCloseWrappedProvider(wp), nil\n\t}\n\n\tprovider := bc.starter(opts)\n\tif provider == nil {\n\t\treturn nil, fmt.Errorf(\"provider(%q, %q).Build(%v) failed\", sk.name, sk.config, opts)\n\t}\n\twp := &wrappedProvider{\n\t\tProvider: provider,\n\t\trefCount: 1,\n\t\tstoreKey: sk,\n\t\tstore:    provStore,\n\t}\n\tprovStore.providers[sk] = wp\n\treturn newSingleCloseWrappedProvider(wp), nil\n}\n\n// String returns the provider name and config as a colon separated string.\nfunc (bc *BuildableConfig) String() string {\n\treturn fmt.Sprintf(\"%s:%s\", bc.name, string(bc.config))\n}\n\n// ParseConfig is a convenience function to create a BuildableConfig given a\n// provider name and configuration. Returns an error if there is no registered","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/tls/certprovider/store.go#L142-L178","documentation":"BuildableConfig.Build() invokes the provider's starter function to create a Provider instance; if the starter returns nil, Build returns this error. The starter is set by the plugin's ParseConfig; for the bundled pemfile plugin newProvider never returns nil, so this is effectively a plugin-author bug or a third-party provider whose factory deliberately returned nil (e.g. internal preconditions not met).","triggerScenarios":"A custom registered certprovider plugin whose starter closure returns nil for certain BuildOptions; a forked provider whose newProvider has an unhandled early-return path.","commonSituations":"Writing a custom certificate provider plugin and forgetting to return the constructed provider; an Options combo that the provider does not support leading to a nil return.","solutions":["If you are the plugin author: audit the starter function for any path that returns nil and return a real error instead so the failure is diagnosable.","If you are a user: upgrade or patch the offending provider plugin; verify BuildOptions (CertName, WantRoot, WantIdentity) match what the provider supports.","Switch to the bundled file_watcher or another supported provider name for which Build is guaranteed non-nil."],"exampleFix":"// before (custom plugin)\nstarter := func(o certprovider.BuildOptions) certprovider.Provider {\n    if !o.WantIdentity { return nil } // -> Build() failed\n    return newProv(o)\n}\n\n// after\nstarter := func(o certprovider.BuildOptions) certprovider.Provider {\n    return newProv(o) // never return nil; surface errors at Build via a wrapping provider","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"p, err := bc.Build(opts)\nif err != nil {\n    return fmt.Errorf(\"provider %s build failed (starter returned nil): %w\", bc.String(), err)\n}","preventionTips":["If you author a provider plugin, never return nil from the starter; return a real error.","Cover every BuildOptions combination in provider tests so no path yields nil.","Pin to a known-good provider version in go.mod."],"tags":["grpc","certprovider","plugin","build","internal"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}