JuliusBrussee/caveman · error

compat upstream name

Error message

compat upstream name %q is reserved

What it means

After passing the name grammar, ValidateName rejects the reserved names "stub" and "openai-compatible", which are the built-in default compat mounts. Using them for a custom mount would collide with the built-in /compat/stub and /compat/openai-compatible routes.

Solutions

  1. Rename the mount to something unique, e.g. "my-openai" or "acme-proxy".
  2. Keep using the built-in /compat/openai-compatible mount instead of defining a custom one with that name.
  3. Use "stub" only for the built-in test stub, never in production config.
  4. Run the startup config validation and rename any entry it flags as reserved.

Example fix

# before
name = "openai-compatible"
# after
name = "my-openai-compatible"
Defensive patterns

Strategy: validation

Validate before calling

var reserved = map[string]bool{"stub": true, "openai-compatible": true}
if reserved[mount.Name] {
    return fmt.Errorf("mount name %q is reserved; pick a unique name", mount.Name)
}

Try / catch

if err := openaicompat.ValidateName(name); err != nil {
    return nil, fmt.Errorf("choose a non-reserved mount name: %w", err)
}

Prevention

When it happens

Trigger: Configuring a named compat mount with name = "stub" or "openai-compatible" and loading it via validateCompat/NewNamedWithWireDialect.

Common situations: Copying the built-in default mount entry to customize it without renaming; config examples that use "stub" as a placeholder name left unchanged.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/487c21ca5b20b72f. Report an issue: GitHub.

Appendix: source

Thrown at proxy/providers/openaicompat/openaicompat.go:426

		// constructs. x-forwarded-*/forwarded/x-real-ip would let a caller
		// choose the client address an upstream rate-limits or allowlists on.
		case "host", "connection", "keep-alive", "proxy-connection", "proxy-authorization", "proxy-authenticate", "te", "trailer", "transfer-encoding", "upgrade", "content-length", "expect",
			"authorization", "x-api-key", "api-key", "x-goog-api-key", "x-goog-user-project", "cookie", "set-cookie",
			"forwarded", "x-forwarded-for", "x-forwarded-host", "x-forwarded-proto", "x-forwarded-port", "x-real-ip",
			"user-agent", "content-type":
			return fmt.Errorf("header %q cannot be forwarded", name)
		}
	}
	return nil
}

func ValidateName(name string) error {
	if !validName.MatchString(name) {
		return fmt.Errorf("compat upstream name %q must match [a-z0-9][a-z0-9._-]{0,63}", name)
	}
	switch name {
	case "stub", "openai-compatible":
		return fmt.Errorf("compat upstream name %q is reserved", name)
	default:
		return nil
	}
}

func ValidateBaseURL(raw string) error {
	_, err := parseBaseURL(raw, "compat")
	return err
}

// ResolveUpstreamURL intentionally ignores RouteContext.BaseURL. Named mounts
// are configured as independent static upstreams (`compat:` in standalone or
// CAVE_COMPAT_UPSTREAMS in managed mode); applying the generic project
// openai_compatible override here would collapse every named route onto one
// target. The default /compat/ adapter is the route that honors BaseURL.
func (a namedAdapter) ResolveUpstreamURL(_ context.Context, req *http.Request, _ providers.RouteContext) (*url.URL, error) {
	if req == nil || req.URL == nil {
		return nil, fmt.Errorf("compat request URL is missing")

View on GitHub (pinned to 3ee70a1026)