JuliusBrussee/caveman · error

compat upstream %q: base_url: %w

Error message

compat upstream %q: base_url: %w

What it means

validateCompat passes base_url through openaicompat.ValidateBaseURL, which parses it as an absolute URL for a compat upstream. The %w chain preserves the underlying parse error, so the message tells you exactly why the URL is unusable (missing scheme, unparseable, wrong scheme).

Source

Thrown at proxy/internal/config/config.go:251

// Runtime endpoint from BedrockRegion. Callers never need to paste a raw URL for
// the normal first-party path.
func (c Config) BedrockBaseURL() string {
	if configured := c.BaseURL("bedrock", ""); configured != "" {
		return configured
	}
	return fmt.Sprintf("https://bedrock-runtime.%s.amazonaws.com", c.BedrockRegion())
}

func (c Config) validateCompat() error {
	for name, upstream := range c.Compat {
		if err := openaicompat.ValidateName(name); err != nil {
			return fmt.Errorf("compat upstream %q: %w", name, err)
		}
		if strings.TrimSpace(upstream.BaseURL) == "" {
			return fmt.Errorf("compat upstream %q: base_url is required", name)
		}
		if err := openaicompat.ValidateBaseURL(upstream.BaseURL); err != nil {
			return fmt.Errorf("compat upstream %q: base_url: %w", name, err)
		}
	}
	return nil
}

// providerEnvKey maps a provider name to the BYOK environment variable that
// holds its API key.
var providerEnvKey = map[string]string{
	"anthropic":         "ANTHROPIC_API_KEY",
	"openai":            "OPENAI_API_KEY",
	"gemini":            "GEMINI_API_KEY",
	"azure_openai":      "AZURE_OPENAI_API_KEY",
	"openai_compatible": "OPENAI_COMPAT_API_KEY",
}

// Credential returns the provider credential resolved from the environment.
// Empty means the caller must fail closed or use an explicit inbound credential.
// Bedrock bearer keys are PAYG credentials even though their wire scheme is

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Prefix the scheme: base_url: https://openrouter.ai/api/v1
  2. Use http:// only for deliberate local test doubles; https:// for real upstreams
  3. Trim stray quotes, spaces, or newlines from env-provided URLs
  4. Read the wrapped (%w) parse error text — it names the exact URL defect

Example fix

# before
compat:
  openrouter:
    base_url: openrouter.ai/api/v1   # Error[1064]: no scheme

# after
compat:
  openrouter:
    base_url: https://openrouter.ai/api/v1
Defensive patterns

Strategy: validation

Validate before calling

func usableBaseURL(raw string) bool {
    u, err := url.Parse(strings.TrimSpace(raw))
    return err == nil && u.IsAbs() && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
}

for name, up := range cfg.Compat {
    if !usableBaseURL(up.BaseURL) { /* reject config */ }
}

Prevention

When it happens

Trigger: base_url like 'openrouter.ai/api/v1' (no scheme), 'ftp://...' (wrong scheme), a URL with spaces or control characters, or one that net/url.Parse rejects (e.g. embedded credentials with invalid escapes).

Common situations: Omitting https:// when pasting from a browser bar; trailing '/v1' path confusion is fine but scheme omission is not; env var with stray quotes or newline characters.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/e8416d745dc89c43. Report an issue: GitHub.