JuliusBrussee/caveman · error
header cannot be forwarded
Error message
header %q cannot be forwarded
What it means
ValidateForwardHeaders rejects any header name that is not a syntactically valid HTTP field name (httpguts.ValidHeaderFieldName) or that begins with the proxy-reserved prefixes x-cave- or x-caveman-. Such headers would let a mount override routing, message framing, or Caveman credentials, so construction fails with "header %q cannot be forwarded".
Solutions
- Remove the x-cave-*/x-caveman-* prefixed header from forward_headers — these are proxy-internal.
- Fix the header name syntax: valid token characters only, no colons/spaces, non-empty.
- Forward a neutral custom name instead if you need a metadata header.
- Validate names with httpguts.ValidHeaderFieldName before adding them to config.
Example fix
// before
headers := []string{"X-Caveman-Trace-Id"}
// after
headers := []string{"X-Trace-Id"} Defensive patterns
Strategy: validation
Validate before calling
func forwardable(name string) bool {
lower := strings.ToLower(name)
return httpguts.ValidHeaderFieldName(name) &&
!strings.HasPrefix(lower, "x-cave-") &&
!strings.HasPrefix(lower, "x-caveman-")
} Type guard
func isProxyReservedHeader(name string) bool {
lower := strings.ToLower(name)
return strings.HasPrefix(lower, "x-cave-") || strings.HasPrefix(lower, "x-caveman-")
} Try / catch
if err := openaicompat.ValidateForwardHeaders(headers); err != nil {
var bad string
fmt.Sscanf(err.Error(), "header %q", &bad)
headers = slices.DeleteFunc(headers, func(h string) bool { return h == bad })
} Prevention
- Treat x-cave-*/x-caveman-* namespaces as proxy-internal, never forward them.
- Validate header tokens before writing them to config.
- Strip trailing colons/spaces when importing header lists.
When it happens
Trigger: A name in the forward_headers list fails httpguts.ValidHeaderFieldName (invalid characters, empty) or is prefixed x-cave-/x-caveman- (case-insensitive), evaluated in the first branch of the loop before the denylist switch.
Common situations: Header names with spaces or non-ASCII characters from hand-edited config; attempting to spoof the proxy's own x-caveman-* internal headers; copy-pasted names with trailing colons like "X-Request-Id:".
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
- compat upstream forward_headers
- compat route path rejected
- compat upstream name
- request URL is missing
- wire dialect must be "anthropic" or empty
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/6579a0bc5590c6eb.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/providers/openaicompat/openaicompat.go:404
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.
func ValidateForwardHeaders(names []string) error {
for _, name := range names {
lower := strings.ToLower(name)
if !httpguts.ValidHeaderFieldName(name) || strings.HasPrefix(lower, "x-cave-") || strings.HasPrefix(lower, "x-caveman-") {
return fmt.Errorf("header %q cannot be forwarded", name)
}
switch lower {
// Routing/framing, credentials, and fields whose value this proxy
// 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)View on GitHub (pinned to 3ee70a1026)