{"record":{"id":"773a7c6c6698960b","repo":"alibaba/open-code-review","slug":"custom-provider-q-requires-a-url-field-for-protoc","errorCode":null,"errorMessage":"custom provider %q requires a url field for protocol %q","messagePattern":"custom provider %q requires a url field for protocol %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/llm/resolver.go","lineNumber":460,"sourceCode":"\t\t\t\treturn ResolvedEndpoint{}, false, fmt.Errorf(\"provider %q: %w\", cfg.Provider, err)\n\t\t\t}\n\t\t\tprotocol = normalized\n\t\t}\n\t} else {\n\t\t// Custom provider: protocol is always required; model can come from\n\t\t// cfg.Model. url is required for every protocol that names an HTTP\n\t\t// endpoint, which is all of them except bedrock — there the region\n\t\t// decides the host, so demanding a url would mean storing a value the\n\t\t// client never reads.\n\t\tif entry.Protocol == \"\" {\n\t\t\treturn ResolvedEndpoint{}, false, fmt.Errorf(\"custom provider %q requires a protocol field\", cfg.Provider)\n\t\t}\n\t\tnormalized := NormalizeProtocol(entry.Protocol)\n\t\tif err := ValidateProtocol(normalized); err != nil {\n\t\t\treturn ResolvedEndpoint{}, false, fmt.Errorf(\"custom provider %q: %w\", cfg.Provider, err)\n\t\t}\n\t\tif normalized != ProtocolAnthropicBedrock && entry.URL == \"\" {\n\t\t\treturn ResolvedEndpoint{}, false, fmt.Errorf(\"custom provider %q requires a url field for protocol %q\", cfg.Provider, normalized)\n\t\t}\n\t\turl = entry.URL\n\t\tprotocol = normalized\n\t}\n\n\t// Ambient auth follows the protocol actually in force, which is why this is\n\t// resolved after the override above rather than read off the preset. A preset\n\t// declares ambient auth (AmbientAuth), but an entry may override the preset's\n\t// protocol: a bedrock preset switched to \"openai\" speaks a protocol with no\n\t// SigV4 signing and needs a token like anything else. Conversely an entry\n\t// that selects the bedrock protocol explicitly signs its requests whatever\n\t// the preset says.\n\tambientAuth := protocol == ProtocolAnthropicBedrock ||\n\t\t(isPreset && preset.AmbientAuth && entry.Protocol == \"\")\n\n\t// No credential at all is an error, and it is reported before api_key_cmd\n\t// runs: only the command's *execution* is deferred, not the emptiness check.\n\t// An ambient-auth provider is the exception — it has no key to configure,","sourceCodeStart":442,"sourceCodeEnd":478,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/llm/resolver.go#L442-L478","documentation":"The resolver builds an endpoint for a custom (non-preset) provider entry. Every protocol except anthropic-bedrock names a concrete HTTP endpoint, so a custom provider entry must carry a `url` field; bedrock is exempt because the AWS region determines the host. This error means a custom provider entry declared a valid, non-bedrock protocol but left `url` empty.","triggerScenarios":"A [providers] entry without a preset (custom provider) sets `protocol` (e.g. \"openai\" or \"anthropic\") but omits `url`, and `ocr review` (via tryOCRConfig -> tryProviderConfig) resolves the endpoint.","commonSituations":"Adding a new custom/self-hosted provider (vLLM, Ollama, OpenRouter) and forgetting the url; copying an existing bedrock entry (which legitimately has no url) and changing only the protocol; typo-ing the url key so the entry field parses as empty.","solutions":["Add a `url` field to the provider entry, e.g. url = \"https://api.example.com/v1\"","If the provider really is AWS Bedrock, set protocol = \"anthropic-bedrock\" (url is then not required; configure region instead)","Check the key spelling in the config — the url must be inside the same provider entry block"],"exampleFix":"// before (config.toml)\n[providers.my-llm]\nprotocol = \"openai\"\nmodel = \"llama3\"\n\n// after\n[providers.my-llm]\nprotocol = \"openai\"\nurl = \"http://localhost:8000/v1\"\nmodel = \"llama3\"","handlingStrategy":"validation","validationCode":"// Go: check a custom provider entry before resolution\nfunc validateCustomProvider(name, protocol, url string) error {\n\tif protocol == \"\" {\n\t\treturn fmt.Errorf(\"provider %q: missing protocol\", name)\n\t}\n\tif strings.EqualFold(protocol, \"anthropic-bedrock\") {\n\t\treturn nil // url not required for bedrock\n\t}\n\tif url == \"\" {\n\t\treturn fmt.Errorf(\"provider %q: protocol %q requires url\", name, protocol)\n\t}\n\treturn nil\n}","typeGuard":"func hasURLForProtocol(protocol, url string) bool {\n\treturn url != \"\" || strings.EqualFold(protocol, \"anthropic-bedrock\")\n}","tryCatchPattern":"ep, ok, err := resolver.TryProviderConfig(cfg, override)\nif err != nil {\n\tif strings.Contains(err.Error(), \"requires a url field\") {\n\t\tfmt.Fprintf(os.Stderr, \"config error: add url for provider: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n\treturn err\n}","preventionTips":["Always pair a custom provider's protocol with a url in the same entry block","Never copy bedrock entries (which omit url) as templates for HTTP providers","Validate the ocr config with a dry run before scripting it in CI"],"tags":["config","llm-provider","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}