alibaba/open-code-review · error

custom provider %q requires a protocol field

Error message

custom provider %q requires a protocol field

What it means

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.

Source

Thrown at internal/llm/resolver.go:453

		authHeader = preset.AuthHeader
		if entry.URL != "" {
			url = entry.URL
		}
		if entry.Protocol != "" {
			normalized := NormalizeProtocol(entry.Protocol)
			if err := ValidateProtocol(normalized); err != nil {
				return ResolvedEndpoint{}, false, fmt.Errorf("provider %q: %w", cfg.Provider, err)
			}
			protocol = normalized
		}
	} else {
		// Custom provider: protocol is always required; model can come from
		// cfg.Model. url is required for every protocol that names an HTTP
		// endpoint, which is all of them except bedrock — there the region
		// decides the host, so demanding a url would mean storing a value the
		// client never reads.
		if entry.Protocol == "" {
			return ResolvedEndpoint{}, false, fmt.Errorf("custom provider %q requires a protocol field", cfg.Provider)
		}
		normalized := NormalizeProtocol(entry.Protocol)
		if err := ValidateProtocol(normalized); err != nil {
			return ResolvedEndpoint{}, false, fmt.Errorf("custom provider %q: %w", cfg.Provider, err)
		}
		if normalized != ProtocolAnthropicBedrock && entry.URL == "" {
			return ResolvedEndpoint{}, false, fmt.Errorf("custom provider %q requires a url field for protocol %q", cfg.Provider, normalized)
		}
		url = entry.URL
		protocol = normalized
	}

	// Ambient auth follows the protocol actually in force, which is why this is
	// resolved after the override above rather than read off the preset. A preset
	// declares ambient auth (AmbientAuth), but an entry may override the preset's
	// protocol: a bedrock preset switched to "openai" speaks a protocol with no
	// SigV4 signing and needs a token like anything else. Conversely an entry
	// that selects the bedrock protocol explicitly signs its requests whatever

View on GitHub (pinned to 5cf97d0d15)

Solutions

  1. Add "protocol" to the custom provider entry: "anthropic", "openai", "openai-responses", or "anthropic-bedrock"
  2. If the endpoint is OpenAI-compatible, use "protocol": "openai"
  3. If the backend is actually a known vendor, use its preset name under "providers" instead of custom_providers

Example fix

// before
{ "provider": "my-gateway", "custom_providers": { "my-gateway": { "url": "https://gw.internal/v1", "api_key": "sk-..." } } }

// after
{ "provider": "my-gateway", "custom_providers": { "my-gateway": { "url": "https://gw.internal/v1", "api_key": "sk-...", "protocol": "openai" } } }
Defensive patterns

Strategy: validation

Validate before calling

var cfg struct {
    CustomProviders map[string]struct{ Protocol string `json:"protocol"` } `json:"custom_providers"`
}
json.Unmarshal(data, &cfg)
for name, e := range cfg.CustomProviders {
    if e.Protocol == "" {
        return fmt.Errorf("custom provider %q is missing required field \"protocol\"", name)
    }
}

Prevention

When it happens

Trigger: config.json has "provider": "X" where X is not a known preset, and cfg.CustomProviders["X"].Protocol is empty or omitted.

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02). Data as JSON: /api/errors/f8c467fa4aaa3897. Report an issue: GitHub.