Skip to content

Fix/header override suppress empty#4996

Open
yingfengli wants to merge 3 commits into
QuantumNous:mainfrom
yingfengli:fix/header-override-suppress-empty
Open

Fix/header override suppress empty#4996
yingfengli wants to merge 3 commits into
QuantumNous:mainfrom
yingfengli:fix/header-override-suppress-empty

Conversation

@yingfengli
Copy link
Copy Markdown

@yingfengli yingfengli commented May 20, 2026

Summary

Fixes two related bugs that prevent operators from suppressing a header on a channel via the header_override setting.

Bug 1: empty value silently dropped

header_override = {"anthropic-beta": ""} was a no-op. sanitizeHeaderOverrideMap and applyHeaderOverridePlaceholders both treated empty strings as "no entry," so the channel's existing header (e.g. anthropic-beta copied from the client by CommonClaudeHeadersOperation) leaked through unchanged.

This commit treats an empty value as an explicit suppression marker: the entry is retained through sanitization, and downstream consumers (applyHeaderOverrideToRequest, DoWssRequest, doAwsClientRequest, buildFetchModelsHeaders) call Header.Del(key) instead of Header.Set(key, ""). The passthrough rule keys (*, re:..., regex:...) historically also use empty values; their behavior is preserved by the same fall-through.

Bug 2: runtime override silently replaced channel override

When info.UseRuntimeHeadersOverride was true (set by upstream features such as the built-in claude cli trace channel-affinity rule), GetEffectiveHeaderOverride returned only RuntimeHeadersOverride and discarded the operator-configured channel header_override entirely.

For the Bedrock case this meant the affinity rule re-injected anthropic-beta from the client request after the operator had explicitly tried to suppress it on the channel.

This commit merges the two: runtime first, channel-level on top. Channel-level entries (including empty-string suppression markers) win for keys defined in both layers, on the principle that the admin UI is the authoritative source of header policy. Runtime-only entries are still included via the union merge.

Why this matters

Concrete failure mode this fixes: Claude Code → New API → AWS Bedrock channel.

Bedrock rejects beta flags Anthropic ships first-party (prompt-caching-2024-07-31, claude-code-20250219, etc.) with ValidationException: invalid beta flag. Without this fix there is no way for an operator to suppress those headers on the Bedrock channel, even though the UI exposes a setting that appears to do exactly that.

Test plan

  • go build ./... clean
  • All existing header_override tests pass
  • New tests:
    • TestProcessHeaderOverride_EmptyValueIsExplicitSuppression — sanitizer keeps empty values
    • TestApplyHeaderOverrideToRequest_EmptyValueDeletesHeaderDel() is called instead of Set("", "")
    • TestProcessHeaderOverride_ChannelOverrideWinsOverRuntime — channel beats runtime in the merge
    • TestGetEffectiveHeaderOverrideMergesChannelOnTopOfRuntime — empty-string suppression marker survives the merge
  • End-to-end: Claude Code → New API → Bedrock with header_override = {"anthropic-beta": ""} on the Bedrock channel returns 200 OK; affinity rule still triggers but channel override wins; anthropic_beta no longer appears in upstream Bedrock body.

Files changed

  • relay/common/override.go — sanitizer + GetEffectiveHeaderOverride merge
  • relay/common/override_test.go — merge test updated
  • relay/channel/api_request.go — placeholder expansion + applyHeaderOverrideToRequest + DoWssRequest Del logic
  • relay/channel/api_request_test.go — new suppression / merge tests
  • relay/channel/aws/relay-aws.go — Bedrock Del logic
  • controller/channel.go — channel-test path Del logic

Notes

Summary by CodeRabbit

  • Bug Fixes

    • Empty-string header overrides now explicitly suppress/remove headers (won't forward empty values) across HTTP, WebSocket, and cloud client requests.
    • Channel-level header configurations consistently take precedence over runtime-level overrides.
  • New Features

    • Added a mechanism for adapters to obtain the computed header-override map for integration and debugging purposes.

Review Change Stack

Setting a header_override entry to "" is the natural way to express
"do not forward this header to the upstream", but the previous code
silently dropped empty entries:

- sanitizeHeaderOverrideMap discarded keys whose value was empty unless
  the key happened to be a passthrough rule key ("*", "re:", "regex:").
- applyHeaderOverridePlaceholders returned include=false for empty
  templates.

As a result, a header that the channel adaptor already wrote into the
outgoing request from c.Request.Header (notably anthropic-beta, which
claude.CommonClaudeHeadersOperation copies for the AWS Bedrock channel)
would still be forwarded to the upstream regardless of the override.

This caused requests routed through a Bedrock channel to fail with
"ValidationException: invalid beta flag" whenever the client sent any
of the beta flags Bedrock does not accept (prompt-caching-2024-07-31,
extended-cache-ttl-2025-04-11, etc.) - which is the default behavior of
clients like Claude Code. Operators had no configuration-only way to
fix it.

Fix:

- sanitizeHeaderOverrideMap now preserves empty values for any key
  (passthrough rule contract is unchanged).
- applyHeaderOverridePlaceholders returns include=true for empty
  templates so the suppression marker reaches the consumers.
- All four header_override consumers - applyHeaderOverrideToRequest,
  DoWssRequest's loop, the AWS Bedrock relay, and channel test header
  building in controller/channel.go - now call Header.Del when the
  override value is "" instead of writing the empty string.

This restores the obvious mental model:

| header_override value           | upstream behavior   |
| ------------------------------- | ------------------- |
| "anthropic-beta": "value"       | Set to "value"      |
| "anthropic-beta": ""            | Removed (NEW)       |
| (no entry)                      | Pass-through        |

Backward compatibility: production overrides almost universally use
non-empty values, and intentionally storing an empty value to mean
"no-op" was never a documented contract; the new semantics match
operator intent.

Tests:

- TestProcessHeaderOverride_EmptyValueIsExplicitSuppression
- TestApplyHeaderOverrideToRequest_EmptyValueDeletesHeader

Both regress against the previous behavior; all existing
header_override tests still pass.
…override

Before this change, GetEffectiveHeaderOverride returned the runtime
override map verbatim whenever info.UseRuntimeHeadersOverride was true,
completely ignoring the channel-level header_override configured in the
admin UI. The runtime override map is populated by upstream features
such as channel affinity rules (e.g. the built-in "claude cli trace"
rule, which copies anthropic-beta and other Claude CLI headers into the
runtime map). As a result, an operator setting
header_override = {"anthropic-beta": ""} on a Bedrock channel had no
effect: the affinity rule re-injected the client's anthropic-beta into
the runtime map, downstream consumers wrote it into the upstream
request, and Bedrock rejected the request with
"ValidationException: invalid beta flag" for any flag it does not
support.

Fix:

- Merge the channel-level header_override on top of the runtime override
  map, with channel entries winning on conflicting keys. The admin UI
  is now the authoritative source for header policy on a per-channel
  basis, and runtime-only headers (those the affinity rule injects but
  the channel does not redefine) still flow through unchanged.
- Combined with the previous suppression-marker change, this means
  setting header_override = {"anthropic-beta": ""} on a Bedrock channel
  now reliably strips the header before the upstream call, even when an
  affinity rule attempts to copy it from the client request.

Tests:

- TestGetEffectiveHeaderOverrideMergesChannelOnTopOfRuntime replaces the
  previous TestGetEffectiveHeaderOverrideUsesRuntimeOverrideAsFinalResult
  to encode the new merge semantics.
- TestProcessHeaderOverride_ChannelOverrideWinsOverRuntime replaces the
  previous TestProcessHeaderOverride_RuntimeOverrideIsFinalHeaderMap.
- All other header_override tests continue to pass.

Manually verified end-to-end with the official Claude Code CLI routed
to a Bedrock channel: prior to this change, the request failed with
"invalid beta flag"; after this change, the upstream payload no longer
contains anthropic_beta and the request succeeds.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f6b5362-da06-4b52-bbfe-b800a79084df

📥 Commits

Reviewing files that changed from the base of the PR and between 467ef8f and ce1de3f.

📒 Files selected for processing (4)
  • controller/channel.go
  • relay/channel/api_request.go
  • relay/channel/aws/relay-aws.go
  • relay/common/override.go

Walkthrough

Treat empty-string header overrides as explicit delete directives and merge channel-level overrides over runtime overrides. Changes update sanitization, HTTP/WebSocket application, controller and AWS request handling, and tests.

Changes

Header Override Empty-String Suppression

Layer / File(s) Summary
Core override map semantics and merge logic
relay/common/override.go, relay/common/override_test.go
Empty-string values in override maps are preserved as suppression markers. GetEffectiveHeaderOverride sanitizes runtime overrides then layers channel-level overrides on top so channel entries win conflicts.
HTTP and WebSocket request override application
relay/channel/api_request.go, relay/channel/api_request_test.go
applyHeaderOverridePlaceholders retains empty resolved values as suppression markers; applyHeaderOverrideToRequest, DoWssRequest, and tests delete headers when override values are empty. Adds ResolveHeaderOverride wrapper. Tests updated to assert channel-level overrides win and to cover empty-value suppression behavior.
Platform-specific header override application
controller/channel.go, relay/channel/aws/relay-aws.go
Controller model-fetch header construction and AWS Bedrock request logic apply channel-level overrides after setup and delete headers when override values are empty instead of forwarding empty values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • QuantumNous/new-api#2735: Adds placeholder-based header override processing that this PR extends with empty-string suppression semantics.
  • QuantumNous/new-api#1644: Introduced plumbing for applying header_override into request headers; this PR refines merging and empty-value behavior.
  • QuantumNous/new-api#2840: Modifies related header-override handling and passthrough/ordering logic that overlaps with these changes.

Suggested reviewers

  • seefs001

Poem

🐰 I found a blank string in the stream,

It whispered "delete" — not just a dream.
Channel beats runtime, tidy and clean,
Headers vanish quiet and keen.
A rabbit nods: "Suppression, supreme."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/header override suppress empty' directly addresses the main purpose of the PR—fixing header override suppression with empty string values—and clearly conveys the primary change from the developer's perspective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…is PR

Adds package-level Go doc comments to the seven functions modified by this
branch so they explain the empty-value suppression contract and the
channel-over-runtime merge semantics directly at the function definition,
rather than only at the call sites.

No behavior change. Only doc comments added. Build clean, all existing
header_override tests still pass.

Functions documented:
- controller/channel.go:           buildFetchModelsHeaders
- relay/channel/api_request.go:    applyHeaderOverridePlaceholders,
                                   ResolveHeaderOverride,
                                   applyHeaderOverrideToRequest,
                                   DoWssRequest
- relay/channel/aws/relay-aws.go:  doAwsClientRequest
- relay/common/override.go:        sanitizeHeaderOverrideMap,
                                   GetEffectiveHeaderOverride
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant