{"record":{"id":"f8c467fa4aaa3897","repo":"alibaba/open-code-review","slug":"custom-provider-q-requires-a-protocol-field","errorCode":null,"errorMessage":"custom provider %q requires a protocol field","messagePattern":"custom provider %q requires a protocol field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/llm/resolver.go","lineNumber":453,"sourceCode":"\t\tauthHeader = preset.AuthHeader\n\t\tif entry.URL != \"\" {\n\t\t\turl = entry.URL\n\t\t}\n\t\tif entry.Protocol != \"\" {\n\t\t\tnormalized := NormalizeProtocol(entry.Protocol)\n\t\t\tif err := ValidateProtocol(normalized); err != nil {\n\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","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/llm/resolver.go#L435-L471","documentation":"A custom provider (one without a preset) must always declare a \"protocol\" field, because unlike presets there is no built-in default to fall back to. tryProviderConfig raises this error when a custom_providers entry has an empty protocol. Protocol is what decides URL requirements and auth behavior, so it cannot be inferred.","triggerScenarios":"config.json has \"provider\": \"X\" where X is not a known preset, and cfg.CustomProviders[\"X\"].Protocol is empty or omitted.","commonSituations":"Adding a new self-hosted/OpenAI-compatible gateway and only filling in url and api_key, assuming the protocol is guessed from the URL; copying a preset entry shape into custom_providers.","solutions":["Add \"protocol\" to the custom provider entry: \"anthropic\", \"openai\", \"openai-responses\", or \"anthropic-bedrock\"","If the endpoint is OpenAI-compatible, use \"protocol\": \"openai\"","If the backend is actually a known vendor, use its preset name under \"providers\" instead of custom_providers"],"exampleFix":"// before\n{ \"provider\": \"my-gateway\", \"custom_providers\": { \"my-gateway\": { \"url\": \"https://gw.internal/v1\", \"api_key\": \"sk-...\" } } }\n\n// after\n{ \"provider\": \"my-gateway\", \"custom_providers\": { \"my-gateway\": { \"url\": \"https://gw.internal/v1\", \"api_key\": \"sk-...\", \"protocol\": \"openai\" } } }","handlingStrategy":"validation","validationCode":"var cfg struct {\n    CustomProviders map[string]struct{ Protocol string `json:\"protocol\"` } `json:\"custom_providers\"`\n}\njson.Unmarshal(data, &cfg)\nfor name, e := range cfg.CustomProviders {\n    if e.Protocol == \"\" {\n        return fmt.Errorf(\"custom provider %q is missing required field \\\"protocol\\\"\", name)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat protocol as a mandatory field for every custom_providers entry","Use \"openai\" for any OpenAI-compatible gateway","If the backend matches a preset, configure it under \"providers\" instead"],"tags":["configuration","custom-provider","protocol"],"backgroundTag":"missing-required-argument","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}