JuliusBrussee/caveman · error

compat upstream forward_headers

Error message

compat upstream %q forward_headers: %w

What it means

NewNamedWithWireDialect validates the mount's forward_headers list with ValidateForwardHeaders and wraps failures as "compat upstream %q forward_headers: %w". Any header name that is not a valid HTTP header field name, starts with x-cave-/x-caveman-, or is in the denylist triggers this during mount construction.

Solutions

  1. Remove protected headers (auth, host, content-type, hop-by-hop, x-cave-*/x-caveman-*, x-forwarded-*) from forward_headers — authentication is handled by the credential mapper.
  2. Forward only safe custom headers your upstream actually needs (e.g. "x-request-id", "x-tenant").
  3. Use lowercase valid header names without spaces.
  4. Run ValidateForwardHeaders on the list before constructing the adapter to get a precise failing name.

Example fix

# before
forward_headers = ["Authorization", "X-Request-Id"]
# after
forward_headers = ["X-Request-Id"]
Defensive patterns

Strategy: validation

Validate before calling

for _, h := range forwardHeaders {
    if err := openaicompat.ValidateForwardHeaders([]string{h}); err != nil {
        return fmt.Errorf("drop or rename %q: %w", h, err)
    }
}

Try / catch

if err := openaicompat.ValidateForwardHeaders(headers); err != nil {
    return fmt.Errorf("filter forward_headers before mount creation: %w", err)
}

Prevention

When it happens

Trigger: Passing forwardHeaders containing e.g. "Authorization", "X-Caveman-User", "Content-Type", "Host", or a syntactically invalid name (spaces, colon) to NewNamedWithWireDialect/NewNamed, or listing them in config forward_headers.

Common situations: Trying to forward credentials explicitly instead of using the credential mapper; copying curl-style header lists including Content-Type/User-Agent; hopping headers like Connection or Transfer-Encoding pasted from a debug session.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

//     requests and keep the OpenAI grammar on every other path (see
//     anthropicWireZones): the Anthropic extractor keys the live/frozen
//     boundary on the request's own cache_control breakpoints.
//
// Routing, header mapping, telemetry provider, and pricing keep the mount's
// openai_compatible identity in every dialect.
func NewNamedWithWireDialect(name, baseURL, wireDialect string, forwardHeaders ...string) (providers.Adapter, error) {
	if err := ValidateName(name); err != nil {
		return nil, err
	}
	if err := ValidateWireDialect(wireDialect); err != nil {
		return nil, fmt.Errorf("compat upstream %q: %w", name, err)
	}
	baseURL = strings.TrimSpace(baseURL)
	if err := ValidateBaseURL(baseURL); err != nil {
		return nil, fmt.Errorf("compat upstream %q base_url: %w", name, err)
	}
	if err := ValidateForwardHeaders(forwardHeaders); err != nil {
		return nil, fmt.Errorf("compat upstream %q forward_headers: %w", name, err)
	}
	prefix := "/compat/" + name
	return namedAdapter{
		Base: providers.Base{
			Provider:      "openai_compatible",
			BaseURL:       baseURL,
			Routes:        []string{prefix + "/"},
			UsageProvider: wireDialectUsageProvider[wireDialect],
		},
		prefix:         prefix,
		forwardHeaders: append([]string(nil), forwardHeaders...),
		wireDialect:    wireDialect,
	}, nil
}

// ValidateForwardHeaders permits explicit provider-specific headers without
// letting a mount override routing, message framing, or Caveman credentials.
// Standard provider authentication is handled by the credential mapper.

View on GitHub (pinned to 3ee70a1026)