JuliusBrussee/caveman · error

compat request URL is missing

Error message

compat request URL is missing

What it means

ResolveUpstreamURL in the OpenAI-compatible adapter requires an *http.Request with a non-nil URL to compute the upstream target. If either is nil, there is nothing to resolve and the adapter fails closed rather than guessing. This is an internal contract check guarding the proxy request path.

Solutions

  1. Ensure the request passed to ResolveUpstreamURL was created via http.NewRequest and that its error return was checked
  2. Set req.URL explicitly before calling the adapter (req.URL = parsedURL)
  3. Log the error from http.NewRequest instead of discarding it
  4. In tests, use httptest.NewRequest which always sets a URL

Example fix

// before
req, _ := http.NewRequestWithContext(ctx, "POST", rawURL, body)
url, err := adapter.ResolveUpstreamURL(ctx, req, routeCtx)

// after
req, err := http.NewRequestWithContext(ctx, "POST", rawURL, body)
if err != nil {
    return err
}
url, err := adapter.ResolveUpstreamURL(ctx, req, routeCtx)
Defensive patterns

Strategy: try-catch

Validate before calling

if req == nil || req.URL == nil {
    return fmt.Errorf("request must have a URL before resolving upstream")
}

Type guard

func hasURL(req *http.Request) bool { return req != nil && req.URL != nil }

Try / catch

u, err := adapter.ResolveUpstreamURL(ctx, req, routeCtx)
if err != nil {
    if strings.Contains(err.Error(), "request URL is missing") {
        // rebuild the request via http.NewRequest and retry once
    }
    return err
}

Prevention

When it happens

Trigger: Calling ResolveUpstreamURL with a nil *http.Request, or with a request constructed without a URL (e.g. http.NewRequest returned an error that was ignored, or a hand-built &http.Request{} without setting URL).

Common situations: Middleware or tests building requests manually and forgetting to set the URL field; an earlier http.NewRequest failure silently swallowed upstream of the adapter; passing nil into the adapter from a custom route handler.

Related errors


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

Appendix: source

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

		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")
	}
	if err := validateCompatPath(req.URL.Path, req.URL.RawPath); err != nil {
		return nil, err
	}
	base, err := parseBaseURL(a.BaseURL, a.Provider)
	if err != nil {
		return nil, err
	}
	if !strings.HasPrefix(req.URL.Path, a.prefix+"/") {
		return nil, fmt.Errorf("compat route %q does not match prefix %q", req.URL.Path, a.prefix+"/")
	}
	path := strings.TrimPrefix(req.URL.Path, a.prefix)
	base.Path = joinCompatPath(base.Path, path)
	base.RawPath = ""
	base.RawQuery = joinCompatQuery(base.RawQuery, req.URL.RawQuery)
	return base, nil
}

View on GitHub (pinned to 3ee70a1026)