Repository navigation
fix: preserve provider-required User-Agent on outbound requests, refs #2303 - #2501
Conversation
…ooplj#2303 The outbound transformer for channels like GitHub Copilot sets a provider-required User-Agent (GitHubCopilotChat/...), but it never reached the upstream on real calls: 1. The pipeline merged the inbound client's User-Agent into the outbound request, overwriting the transformer-set value. 2. The user-agent-pass-through middleware then forced the default axonhub/1.0 whenever pass-through was disabled (the default). Channel checks bypass that pipeline, so they showed the correct UA while real traffic went out as axonhub. - llm/httpclient: treat User-Agent as a blocked header in MergeHTTPHeaders so the client UA is never auto-merged upstream; the pass-through middleware remains the single owner of client-UA forwarding. - orchestrator: when pass-through is disabled, apply the axonhub/1.0 default only when the transformer set no User-Agent. Pass-through enabled still forwards the client UA, and channel override headers still run afterwards.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (5)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe change prevents client User-Agent headers from being merged automatically. It preserves User-Agent values set by outbound transformers when pass-through is disabled. Tests cover both disabled and enabled pass-through behavior. ChangesUser-Agent forwarding
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ooplj#2303 (looplj#2501) The outbound transformer for channels like GitHub Copilot sets a provider-required User-Agent (GitHubCopilotChat/...), but it never reached the upstream on real calls: 1. The pipeline merged the inbound client's User-Agent into the outbound request, overwriting the transformer-set value. 2. The user-agent-pass-through middleware then forced the default axonhub/1.0 whenever pass-through was disabled (the default). Channel checks bypass that pipeline, so they showed the correct UA while real traffic went out as axonhub. - llm/httpclient: treat User-Agent as a blocked header in MergeHTTPHeaders so the client UA is never auto-merged upstream; the pass-through middleware remains the single owner of client-UA forwarding. - orchestrator: when pass-through is disabled, apply the axonhub/1.0 default only when the transformer set no User-Agent. Pass-through enabled still forwards the client UA, and channel override headers still run afterwards.
…g client headers (#2635) (#2638) The channel-level and global "pass through User-Agent" switch was inert for codex channels because of two stacked causes: 1. The codex outbound transformer unconditionally copied the inbound client User-Agent back onto the outbound request (both the Responses path and the mirrored alpha-search path), so the client fingerprint always won before any policy layer ran. 2. After #2501 the orchestrator middleware only applies the axonhub default when the outbound request has no User-Agent at all. Since the transformer always produced one, the disabled branch never applied. Fix the transformer side only, keeping #2501's contract intact: the transformer now owns the UA and presents a Codex-client-like default (codexDefaultUserAgent = "codex_cli_rs/" + codexDefaultVersion), symmetric with how Version already defaults to codexDefaultVersion (#2591). Client-UA forwarding remains solely owned by applyUserAgentPassThrough, whose disabled-branch semantics are unchanged, so a transformer-set provider UA (e.g. GitHubCopilotChat on Copilot channels) is still preserved when the switch is off and the client UA still wins when it is on. Also stop leaking client attribution headers: block the non-standard Http-Referer spelling (the canonical Referer was already blocked) and the client-private X-Zcode-* prefix. X-Title / X-Platform / X-Os-* are deliberately left unblocked because OpenRouter-style channels legitimately use them. Tests: - codex outbound/alpha-search: outbound UA is the codex default and never the inbound client UA, including through the generic inbound-header merge. - orchestrator: switch enabled -> client UA wins over the codex default; switch disabled -> the codex default survives; Copilot-preservation case still passes. - httpclient: Http-Referer and X-Zcode-* are not merged into outbound headers.
…g client headers (looplj#2635) (looplj#2638) The channel-level and global "pass through User-Agent" switch was inert for codex channels because of two stacked causes: 1. The codex outbound transformer unconditionally copied the inbound client User-Agent back onto the outbound request (both the Responses path and the mirrored alpha-search path), so the client fingerprint always won before any policy layer ran. 2. After looplj#2501 the orchestrator middleware only applies the axonhub default when the outbound request has no User-Agent at all. Since the transformer always produced one, the disabled branch never applied. Fix the transformer side only, keeping looplj#2501's contract intact: the transformer now owns the UA and presents a Codex-client-like default (codexDefaultUserAgent = "codex_cli_rs/" + codexDefaultVersion), symmetric with how Version already defaults to codexDefaultVersion (looplj#2591). Client-UA forwarding remains solely owned by applyUserAgentPassThrough, whose disabled-branch semantics are unchanged, so a transformer-set provider UA (e.g. GitHubCopilotChat on Copilot channels) is still preserved when the switch is off and the client UA still wins when it is on. Also stop leaking client attribution headers: block the non-standard Http-Referer spelling (the canonical Referer was already blocked) and the client-private X-Zcode-* prefix. X-Title / X-Platform / X-Os-* are deliberately left unblocked because OpenRouter-style channels legitimately use them. Tests: - codex outbound/alpha-search: outbound UA is the codex default and never the inbound client UA, including through the generic inbound-header merge. - orchestrator: switch enabled -> client UA wins over the codex default; switch disabled -> the codex default survives; Copilot-preservation case still passes. - httpclient: Http-Referer and X-Zcode-* are not merged into outbound headers. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Refs #2303.
Problem
For channels whose outbound transformer sets a provider-required
User-Agent— e.g. GitHub Copilot channels setGitHubCopilotChat/0.26.7viaSetCopilotHeaders— the header never reached the upstream on real requests. Two layers erased it:MergeInboundRequest→MergeHTTPHeaders), andUser-Agentwas not blocked, so the client's UA (e.g.codex_cli_rs/...) overwrote the transformer-set one.user-agent-pass-throughmiddleware then forcedUser-Agent: axonhub/1.0whenever UA pass-through was disabled (the default), discarding any UA the transformer had set.The quota/health checker builds its request directly with
SetCopilotHeadersand bypasses this pipeline (github_copilot_checker.go), which is why checking the channel showed the correctGitHubCopilotChatUA while real traffic went out asaxonhub/1.0. The UI channel-test path does go through the pipeline and was equally affected before this fix.Fix
llm/httpclient: treatUser-Agentas a blocked header inMergeHTTPHeaders, so the inbound client UA is never auto-merged into the outbound request (same treatment as the transformer-ownedAccept). Client-UA forwarding remains owned by the explicit pass-through middleware.internal/server/orchestrator: when pass-through is disabled, the middleware applies theaxonhub/1.0default only when the outbound transformer set noUser-Agent; a transformer-set (provider-required) UA is preserved.Unchanged: pass-through enabled still forwards the client UA; channel header overrides still run after the middleware and can force any UA; transformers that set no UA still get the
axonhub/1.0default.Scope note
Issue #2303 also reports Responses→Chat tool loss for Copilot. On current
unstable, tool declarations already survive that path, and the remaining tool-lifecycle gap is being addressed by open PR #2220 (Responses-to-Chat tool compatibility), so this PR intentionally stays on the UA half to avoid duplicating that work.Tests
TestMergeHTTPHeaders_UserAgentNotMerged(llm/httpclient): client UA must not overwrite or enter outbound headers.TestApplyUserAgentPassThroughcases: transformer-set UA (GitHubCopilotChat/0.26.7) is preserved when pass-through is disabled; client UA still wins when enabled.cd llm && go test ./...passes (one pre-existingwebsocket_executorfailure also fails on a clean checkout of the base on Windows);go test ./internal/server/orchestrator/passes;gofmt/go vetclean on changed files.Summary by CodeRabbit