From 8f9e6622b029164991c6f97b67562f940da327bd Mon Sep 17 00:00:00 2001 From: muzhi1991 <2101044+muzhi1991@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:45:37 +0800 Subject: [PATCH 1/2] fix(util): forward custom Host header to upstream Custom headers configured under openai-compatibility (and any other provider passing through applyCustomHeaders) were silently dropped for the Host key, because Go's net/http reads the wire Host from req.Host, not req.Header["Host"]. As a result, virtual-host routed upstreams (e.g. LiteLLM behind an ingress) saw the base-url's host instead of the user-configured override and returned 404. Detect the Host key with http.CanonicalHeaderKey and assign it to req.Host so it is actually written on the wire. Other headers continue to use Header.Set as before. Fixes #2833 --- internal/util/header_helpers.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/util/header_helpers.go b/internal/util/header_helpers.go index c53c291f..967903fc 100644 --- a/internal/util/header_helpers.go +++ b/internal/util/header_helpers.go @@ -47,6 +47,14 @@ func applyCustomHeaders(r *http.Request, headers map[string]string) { if k == "" || v == "" { continue } + // Host is read from req.Host (not req.Header) by net/http when + // writing the request; setting it via Header.Set is silently + // dropped on the wire. Handle it explicitly so user-configured + // virtual-host overrides actually take effect upstream. + if http.CanonicalHeaderKey(k) == "Host" { + r.Host = v + continue + } r.Header.Set(k, v) } } From eba561bf6f08916a966cef698a5c53c7e273b375 Mon Sep 17 00:00:00 2001 From: muzhi1991 <2101044+muzhi1991@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:28:59 +0800 Subject: [PATCH 2/2] fix(util): also keep Host in header map for synthetic requests Addressing the P1 note from the Codex reviewer: applyCustomHeaders is also called with a synthetic &http.Request{Header: ...} from the websockets executors (aistudio_executor.go, codex_websockets_executor.go), which forward only the header map. The previous continue meant a custom Host was dropped from that map, regressing virtual-host overrides on those flows. Mirror the value to both r.Host (for real net/http) and r.Header (for header-map-only consumers). --- internal/util/header_helpers.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/util/header_helpers.go b/internal/util/header_helpers.go index 967903fc..0b8d72bc 100644 --- a/internal/util/header_helpers.go +++ b/internal/util/header_helpers.go @@ -47,13 +47,13 @@ func applyCustomHeaders(r *http.Request, headers map[string]string) { if k == "" || v == "" { continue } - // Host is read from req.Host (not req.Header) by net/http when - // writing the request; setting it via Header.Set is silently - // dropped on the wire. Handle it explicitly so user-configured - // virtual-host overrides actually take effect upstream. + // net/http reads Host from req.Host (not req.Header) when writing + // a real request, so we must mirror it there. Some callers pass + // synthetic requests (e.g. &http.Request{Header: ...}) and only + // consume r.Header afterwards, so keep the value in the header + // map too. if http.CanonicalHeaderKey(k) == "Host" { r.Host = v - continue } r.Header.Set(k, v) }