Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(headers): per-provider custom upstream and passthrough header overrides#513

Open
weselben wants to merge 23 commits into
ENTERPILOT:mainfrom
weselben:feat/custom-upstream-headers
Open

feat(headers): per-provider custom upstream and passthrough header overrides#513
weselben wants to merge 23 commits into
ENTERPILOT:mainfrom
weselben:feat/custom-upstream-headers

Conversation

@weselben

@weselben weselben commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

GoModel now exposes four per-provider header fields (custom_upstream_headers, passthrough_user_headers, passthrough_user_headers_skip, passthrough_user_headers_skip_mode) wired through the shared CompatibleProvider adapter. Every OpenAI-compatible provider gets static injection, filtered caller-header passthrough, and a new provider-default header layer without per-provider code changes. The default passthrough mode is default-open skip: enabling passthrough_user_headers forwards all non-blocked caller headers unless an explicit allow list is configured.

Files to review (41, +4,124 / -202):

File Why
internal/providers/headers.go (start here) HeaderOverridesConfig, ApplyHeaderOverrides, IsHeaderBlocked, shouldForward, and the four-layer composition engine.
internal/providers/config.go Env-var overlays for the four header fields, fail-closed parseBoolEnv, and skip-mode validation.
internal/server/passthrough_headers.go PassthroughHeaderCapture middleware — filters incoming headers and stashes them in context.
internal/server/request_snapshot.go Removed redundant passthrough capture; PassthroughHeaderCapture owns the hot path now.
internal/providers/openai/compatible_provider.go Propagates HeaderOverrides / UserPathAlias and applies DefaultHeaders before static/passthrough layers.
internal/providers/openrouter/openrouter.go OpenRouter identity headers (HTTP-Referer, X-OpenRouter-Title) now live as DefaultHeaders and flow through the shared engine.
internal/providers/factory.go SetUserPathHeader builder option and plumbing from config to provider options.
internal/providers/init.go anyPassthroughUserHeaders is only set after factory.Create succeeds.
docs/features/custom-headers.mdx Feature guide with four-layer hierarchy, OpenRouter scenarios, and env-var reference.
config/config.example.yaml / .env.template Flat provider examples and env-var documentation.

Why

OpenAI-compatible providers often require headers beyond Authorization to route, attribute, or throttle requests correctly. Operators need to inject static identity headers so internal tooling behind GoModel is identified upstream without every client sending them, and they need to forward selected caller headers such as X-Title or X-Trace-Id when clients are trusted.

The original request surfaced in issue #290. An earlier combined implementation in PR #487 was split: the Kimi Code provider landed in PR #508, and the header-override engine is re-implemented here on top of the PR #486 provider rework.

Design Decisions

  • Shared engine. The header logic lives in internal/providers/headers.go and is applied through CompatibleProvider's single buildHeaderMutator closure, so DeepSeek, Groq, Ollama, vLLM, xAI, OpenRouter, Kimi Code, and any future OpenAI-compatible provider inherit the feature automatically.
  • Four-layer header precedence. On every upstream request, headers are applied in this order; later layers override earlier ones:
    1. Auth headers (Authorization, etc.) from the provider's SetHeaders hook.
    2. Provider default headers (e.g. OpenRouter's HTTP-Referer / X-OpenRouter-Title) supplied via CompatibleProviderConfig.DefaultHeaders.
    3. Static custom_upstream_headers from config.yaml or <PROVIDER>_CUSTOM_UPSTREAM_HEADERS env var.
    4. Caller passthrough headers when passthrough_user_headers is enabled.
  • Provider default headers moved into the shared engine. OpenRouter previously set HTTP-Referer and X-OpenRouter-Title through a local RequestMutator. Those values are now resolved at construction and passed as DefaultHeaders, so they participate in the same precedence rules as static and passthrough headers. This fixes the bug where passthrough headers were overwritten by the local mutator, and it lets operators override attribution with custom_upstream_headers or caller headers without special-casing OpenRouter.
  • Flat provider keys. The four new fields are direct children of each provider block, consistent with the rest of the provider schema. Env vars follow the existing <PROVIDER>_ prefix convention.
  • Default-open skip mode. passthrough_user_headers defaults to false. When enabled with passthrough_user_headers_skip_mode skip or empty (the default), an empty passthrough_user_headers_skip list forwards all non-blocked caller headers. With allow mode and an empty list, no caller headers are forwarded.
  • Fail-closed boolean parsing. parseBoolEnv only accepts an explicit true set (true, 1, yes, on, y) or false set; any unrecognized value defaults to false so typos cannot silently enable passthrough.
  • Middleware only when needed. PassthroughHeaderCapture is registered only when at least one successfully initialized provider has passthrough_user_headers: true, and the flag is recorded after factory.Create succeeds so a failed provider cannot enable the middleware.
  • Hard-coded blocklist plus tagging strip-set. IsHeaderBlocked permanently removes credential headers, X-GoModel-User-Path, the configured user_path alias, and hop-by-hop/transport headers. The passthrough path additionally consults TaggingStripHeadersFromContext so headers marked do_not_pass in tagging rules are never forwarded.
  • Overlap warning. A slog.Warn fires when static and passthrough are both configured and an overlapping header would actually be forwarded; passthrough wins on conflict.

How

  1. RawProviderConfig gains four flat header fields; these are packed into HeaderOverridesConfig during resolution. Env-var overlays apply on top of YAML.
  2. ProviderFactory.SetUserPathHeader stores the alias that IsHeaderBlocked checks before providers.Init runs.
  3. PassthroughHeaderCapture middleware captures the incoming request, strips hard-blocked headers, and stashes the filtered copy in context.
  4. CompatibleProvider receives HeaderOverrides and UserPathAlias in its config, merges provider DefaultHeaders into the override struct, and calls ApplyHeaderOverrides after the provider-specific SetHeaders hook.
  5. ApplyHeaderOverrides applies default headers, then static headers, then passthrough headers. shouldForward checks the blocklist and strip-set before the skip/allow list.
  6. Every OpenAI-compatible provider inherits through the shared adapter. Native SDK providers (Anthropic, Gemini, Azure, Bedrock, Vertex) are not in scope.

Reviewer notes

  • OpenRouter is the canonical provider-default consumer. Verify internal/providers/openrouter/openrouter.go supplies identityHeaders() as DefaultHeaders and that the precedence table in docs/features/custom-headers.mdx matches the behavior.
  • Skip lists only filter caller headers. They do not strip provider default headers; to replace a default, set it explicitly in custom_upstream_headers or rely on passthrough.
  • No double capture. RequestSnapshotCapture no longer captures passthrough headers; PassthroughHeaderCapture owns filtering and context storage.
  • Focus area: the four-layer precedence test in internal/providers/headers_test.go (TestApplyHeaderOverrides_DefaultStaticPassthroughPrecedence) is intended to be the single source of truth for header ordering.

Tests

  • Unit tests for all headers.go paths: static apply, default-header apply, passthrough apply, skip/allow modes, blocklist, strip-set, default-open skip, allow-empty-forward-nothing, overlap warning, and four-layer precedence.
  • Config tests: YAML parsing, env-var override priority, skip mode normalization, comma-separated env values, fail-closed boolean parsing, and invalid skip-mode error propagation.
  • Factory tests: SetUserPathHeader wiring, HeaderOverrides plumbing, and failed-provider passthrough flag guarding.
  • Provider tests: DeepSeek, Groq, Ollama, vLLM, xAI, and Kimi Code verify headers wired via the compatible adapter; OpenRouter tests env/static/caller/default precedence and the passthrough overwrite fix.
  • Server middleware tests: PassthroughHeaderCapture filters blocked headers, context storage, and registration when enabled.
  • App tests: App.New propagates header overrides and AnyPassthroughUserHeaders state.

Run: go test ./internal/providers/... ./internal/server/... ./config/... ./internal/app/... ./run/...

Follow-up

  • Native SDK providers (Anthropic, Gemini, Azure, Bedrock, Vertex) use their own request construction and are not covered. Per-provider audits would be needed to wire ApplyHeaderOverrides there.
  • openai.CompatibleProvider already propagates overrides; the other compatible providers inherit without additional code.

Links

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added per-provider upstream header overrides, plus configurable passthrough of client headers (skip/allow) with user-path alias support and updated precedence rules.
    • Server now conditionally captures and forwards eligible client headers based on provider capability.
  • Bug Fixes

    • Removed bailian from the default passthrough provider list.
    • OpenRouter identity attribution now follows the documented precedence/override behavior.
  • Documentation

    • Updated provider configuration reference and added a Custom Headers feature page (env var parsing, precedence, and hard-blocked headers).
  • Tests

    • Expanded YAML/env parsing, validation, and header precedence/filtering coverage across providers.

weselben added 6 commits July 8, 2026 18:06
Introduce a HeaderOverrides block on each provider that controls how
ingress headers are forwarded upstream. Providers can now declare a
blocklist for sensitive headers, explicit add/replace rules, and a list
of headers to remove before the request leaves the gateway.

This commit is schema-only: the runtime engine and middleware wiring are
added in the following commits.
Implement the runtime header override engine and wire it into the
proxy path. The engine applies provider-specific block, add, replace,
and remove rules to ingress headers before forwarding them upstream.

Provider implementations (OpenAI-compatible, OpenRouter, VLLM, and
Ollama) now use the engine, and the server passthrough middleware
exposes the resolved header set to downstream handlers.
Add unit tests for the header override engine, provider config/factory
wiring, and passthrough middleware. Includes coverage for the
OpenAI-compatible, OpenRouter, VLLM, Ollama, DeepSeek, Groq, Kimi Code,
and xAI provider paths as well as server request snapshot handling.
Add tests for provider-level header override configuration parsing in
config/providers_test.go and extend config/config_test.go to cover the
new schema fields end-to-end.
Add a feature guide for per-provider custom upstream headers, including
blocklist, add, replace, and remove examples. Update config.example.yaml,
.env.template, and CLAUDE.md so users can discover and copy the new
configuration.
The overlap warning is already fully exercised by
TestApplyHeaderOverrides_PassthroughMode_LogsOverlappingStaticHeaders via a
dedicated capturingSlogHandler. captureLogger was never called, so remove it
and the now-unused bytes import to satisfy golangci-lint's unused check.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07c9f9db-bbdf-4b2f-a056-af7f7ba94ff7

📥 Commits

Reviewing files that changed from the base of the PR and between 07f5f04 and b4c4337.

📒 Files selected for processing (3)
  • internal/providers/headers.go
  • internal/providers/headers_test.go
  • internal/providers/openai/compatible_provider_test.go

📝 Walkthrough

Walkthrough

Adds per-provider header override support, filtered passthrough header capture, user-path header wiring, adapter updates, OpenRouter identity precedence, and related configuration and documentation changes.

Changes

Header Overrides Feature

Layer / File(s) Summary
Config schema and env parsing
config/providers.go, internal/providers/config.go, config/providers_test.go, internal/providers/config_test.go, config/config.example.yaml, config/config_test.go, .env.template, CLAUDE.md, docs/features/custom-headers.mdx
Adds YAML/env support for custom upstream headers and passthrough user headers, validates skip modes, updates examples, and documents the feature.
Core header logic
internal/providers/headers.go, internal/providers/headers_test.go
Introduces header override composition, blocking rules, passthrough context storage, and request filtering helpers with coverage for static, passthrough, and skip/allow behavior.
Factory, init, and app wiring
internal/providers/factory.go, internal/providers/init.go, internal/app/app.go, run/lifecycle_test.go, internal/server/http.go, internal/server/request_snapshot.go, internal/server/passthrough_headers.go, internal/server/*_test.go, internal/app/header_overrides_test.go
Threads header override settings and user-path header configuration through provider creation, initialization, startup state, and server middleware wiring.
Provider adapters and tests
internal/providers/openai/compatible_provider.go, internal/providers/openrouter/openrouter.go, internal/providers/deepseek/*, internal/providers/kimicode/*, internal/providers/groq/groq_test.go, internal/providers/xai/xai_test.go, internal/providers/ollama/*, internal/providers/vllm/*
Applies header overrides through provider-specific request paths and tests propagation, blocking behavior, and OpenRouter identity precedence.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant EchoMiddleware
  participant RequestSnapshotCapture
  participant ProviderFactory
  participant Provider
  participant UpstreamAPI

  Client->>EchoMiddleware: HTTP request
  EchoMiddleware->>RequestSnapshotCapture: capture snapshot context
  RequestSnapshotCapture->>RequestSnapshotCapture: filter passthrough headers
  EchoMiddleware->>ProviderFactory: create provider with header settings
  ProviderFactory->>Provider: pass HeaderOverrides and UserPathHeader
  Client->>Provider: ChatCompletion / Passthrough
  Provider->>UpstreamAPI: forward merged headers
  UpstreamAPI-->>Client: response
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#161: Both PRs touch OpenRouter env-driven identity header handling by adding/using OPENROUTER_SITE_URL/OPENROUTER_APP_NAME in the OpenRouter provider.
  • ENTERPILOT/GoModel#330: Both PRs rely on configurable user-path header plumbing through request snapshot and filtering.
  • ENTERPILOT/GoModel#486: Both PRs modify internal/providers/openai/compatible_provider.go and the request/header mutation pipeline.

Poem

I hopped through headers, soft and bright,
With skip and allow tucked out of sight.
A carrot of context, a whisper of care,
And OpenRouter’s identity stayed fair.
🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.67% 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 clearly summarizes the main change: per-provider header overrides for upstream and passthrough behavior.
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.
✨ 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.

The example comments nested the header fields under a header_overrides block
and used the shorthand skip_mode/skip_headers names, neither of which the
parser accepts. Flatten the keys to the real schema
(passthrough_user_headers, passthrough_user_headers_skip_mode,
passthrough_user_headers_skip, custom_upstream_headers) in all three
example providers (openai, groq, deepseek), and correct the stale
header_overrides wording in .env.template.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/providers/init.go (1)

219-272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the passthrough flag behind successful provider creation
anyPassthroughUserHeaders is set before factory.Create, so a config with passthrough_user_headers: true still turns on header capture even if that provider fails to initialize and never becomes active. Move the check after Create succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/init.go` around lines 219 - 272, The
passthrough-user-headers flag is being recorded before provider creation
succeeds, so failed providers can still enable header capture. In
initializeProviders, move the HeaderOverrides.PassthroughUserHeaders check to
after factory.Create returns successfully, and only then update
anyPassthroughUserHeaders for the provider that actually initialized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/config.example.yaml`:
- Around line 269-279: The provider examples use a nested header_overrides block
with skip_mode and skip_headers, but RawProviderConfig expects flat top-level
fields instead. Update the example YAML in the openai, groq, and deepseek
sections to match the actual schema by moving the header fields to the top level
and using the real field names from config/providers.go
(custom_upstream_headers, passthrough_user_headers,
passthrough_user_headers_skip, passthrough_user_headers_skip_mode). Keep the
examples aligned with TestRawProviderConfig_YAMLHeaderFields so uncommenting
them produces the intended header behavior.

In `@internal/providers/config.go`:
- Around line 590-616: The comma-splitting in parseHeaderMapEnv cannot safely
represent header values that contain commas, so update the handling in
parseHeaderMapEnv/config parsing to use a safer format or separator (for example
JSON or another unambiguous encoding) and document the accepted format clearly
near the CUSTOM_UPSTREAM_HEADERS path. Make sure the new behavior is located
through parseHeaderMapEnv and any callers in config.go so operators don’t
silently get malformed header maps when values include commas.
- Around line 575-588: The parseBoolEnv helper currently fails open by treating
any unrecognized value as true, which can silently enable PassthroughUserHeaders
on bad env input. Update parseBoolEnv to use an explicit true-set and return
false or nil for anything else, so only clearly recognized enable values turn
the flag on. Keep the fix localized to parseBoolEnv in
internal/providers/config.go and preserve its existing trimming/lowercasing
behavior.

In `@internal/providers/headers.go`:
- Around line 169-204: Update the docstring for shouldForward to match the
implemented behavior: the current “Default-closed” wording is stale and
contradicts the default-open logic in the switch for mode "skip" and "" when
skipSet is empty. Rewrite the comment above shouldForward (and the nearby mode
handling note if needed) so it clearly states that empty skip sets forward all
headers except those blocked by IsHeaderBlocked or stripSet, and keep the
description consistent with shouldForward and
TestApplyHeaderOverrides_PassthroughMode_DefaultOpenSkipList.
- Around line 105-122: Update logStaticOverridden in
internal/providers/headers.go so it only warns for passthrough headers that
would actually be forwarded upstream after SkipMode filtering. Instead of
checking overlap against the raw PassthroughHeadersFromContext source alone, run
each candidate through the same shouldForward / allow-skip logic used when
building req.Header and only include names that would survive that filter. Keep
the warning and overlapping_names output, but base both on the effective
forwarded headers rather than the unfiltered source.

In `@internal/providers/kimicode/kimicode_headers_test.go`:
- Around line 55-83: TestNew_AppliesUserPathHeader only proves a request was
sent and does not verify the UserPathHeader behavior. Update the test to capture
request headers in the httptest handler, send a passthrough/user-path header
through the ChatCompletion call, and assert that the configured alias
X-Tenant-Path is not forwarded while the request still succeeds. Use the New
constructor, ProviderOptions.UserPathHeader, and provider.ChatCompletion to
locate the behavior.

In `@internal/providers/xai/xai_test.go`:
- Around line 489-499: The X-Custom-Header assertion in xai_test.go is
conditionally skipped when wantCustom is empty, so the default-case test does
not verify the header is absent. Update the header assertions in the relevant
test case to always check gotHeaders.Get("X-Custom-Header") against the expected
value, using the existing tc.wantCustom field and the surrounding gotHeaders
checks for X-Tenant-Path and X-User-Header as the reference points. Ensure the
no-options/default scenario explicitly expects an empty X-Custom-Header value so
stray headers are caught.

In `@internal/server/http.go`:
- Around line 289-293: The middleware wiring in the HTTP server is
double-processing passthrough headers because `RequestSnapshotCapture` is
already followed by `PassthroughHeaderCapture` when
`cfg.PassthroughUserHeadersEnabled` is true. Update the setup around
`e.Use(...)` in `http.go` so `RequestSnapshotCapture` is always called with
passthrough capture disabled, since `PassthroughHeaderCapture` already performs
the needed filtering and context overwrite for both ingress-managed and `/p/*`
routes. Keep the change localized to this middleware registration logic and
preserve the existing `RequestSnapshotCapture` and `PassthroughHeaderCapture`
behavior elsewhere.

---

Outside diff comments:
In `@internal/providers/init.go`:
- Around line 219-272: The passthrough-user-headers flag is being recorded
before provider creation succeeds, so failed providers can still enable header
capture. In initializeProviders, move the HeaderOverrides.PassthroughUserHeaders
check to after factory.Create returns successfully, and only then update
anyPassthroughUserHeaders for the provider that actually initialized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9b0ed2b-e465-467d-91a2-6b2f914eae92

📥 Commits

Reviewing files that changed from the base of the PR and between e0eb0ee and 3f6500c.

📒 Files selected for processing (38)
  • .env.template
  • CLAUDE.md
  • config/config.example.yaml
  • config/config_test.go
  • config/providers.go
  • config/providers_test.go
  • docs/features/custom-headers.mdx
  • internal/app/app.go
  • internal/providers/config.go
  • internal/providers/config_test.go
  • internal/providers/deepseek/deepseek_headers_test.go
  • internal/providers/deepseek/deepseek_test.go
  • internal/providers/factory.go
  • internal/providers/factory_test.go
  • internal/providers/groq/groq_test.go
  • internal/providers/headers.go
  • internal/providers/headers_test.go
  • internal/providers/init.go
  • internal/providers/init_test.go
  • internal/providers/kimicode/kimicode_headers_test.go
  • internal/providers/ollama/ollama.go
  • internal/providers/ollama/ollama_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openai/compatible_provider_test.go
  • internal/providers/openrouter/openrouter.go
  • internal/providers/openrouter/openrouter_test.go
  • internal/providers/vllm/vllm.go
  • internal/providers/vllm/vllm_test.go
  • internal/providers/xai/xai_test.go
  • internal/server/auth_test.go
  • internal/server/http.go
  • internal/server/model_validation_test.go
  • internal/server/passthrough_headers.go
  • internal/server/passthrough_headers_test.go
  • internal/server/passthrough_semantic_enrichment_test.go
  • internal/server/request_snapshot.go
  • internal/server/request_snapshot_test.go
  • run/lifecycle_test.go

Comment thread config/config.example.yaml Outdated
Comment thread internal/providers/config.go
Comment thread internal/providers/config.go
Comment thread internal/providers/headers.go
Comment thread internal/providers/headers.go
Comment thread internal/providers/kimicode/kimicode_headers_test.go
Comment thread internal/providers/xai/xai_test.go Outdated
Comment thread internal/server/http.go Outdated
weselben added 2 commits July 8, 2026 19:04
…ling

- parseBoolEnv now requires an explicit true-set; any unrecognized value
  defaults to false so typos cannot silently enable passthrough.
- logStaticOverridden only warns for headers that survive the same
  skip/allow and strip-set filtering applied during forwarding.
- anyPassthroughUserHeaders is only set after factory.Create succeeds,
  so failed providers cannot enable the middleware.
- Corrected the stale "Default-closed" docstring on shouldForward to
  reflect the actual default-open skip-mode behavior.
…ough capture

- TestNew_AppliesUserPathHeader now captures request headers and asserts
  the configured alias is blocked and user headers are not forwarded by
  default.
- xai_test no longer conditionally skips the X-Custom-Header assertion,
  so the default case explicitly verifies absence.
- RequestSnapshotCapture in server setup is always called with
  capturePassthroughHeaders=false; PassthroughHeaderCapture already handles
  the filtering and context overwrite, avoiding double work on the hot path.
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

This PR has one contained header-forwarding bug that should be fixed before merging.

The main header layering and provider plumbing are coherent, but the new passthrough path currently misses do_not_pass tagging rules because of a case-normalization mismatch.

internal/providers/headers.go

T-Rex T-Rex Logs

What T-Rex did

  • I reproduced the focused Go test that exercises the tagging strip with real WithPassthroughHeaders and ApplyHeaderOverrides, validating the normalization behavior.
  • The reproduction showed a failure where X-Team remained on the upstream request with the value 'payments' despite the tagging strip containing the canonical X-Team key.
  • I created and preserved the reproduction Go test source demonstrating the canonical tagging strip bypass.
  • I inspected the verbose test output and confirmed that X-Team was forwarded despite the strip policy.
  • I reviewed the PR test plan logs and found environment pollution from inherited ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL; after unsetting provider env vars, the sanitized rerun passed with EXIT_CODE 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Server as Echo middleware
participant Ctx as Request context
participant Provider as CompatibleProvider
participant Upstream

Client->>Server: HTTP request with headers
Server->>Ctx: RequestSnapshotCapture stores snapshot/user path
alt passthrough_user_headers enabled
    Server->>Ctx: PassthroughHeaderCapture stores filtered headers
end
Server->>Ctx: TaggingCapture stores labels and do_not_pass strip set
Server->>Provider: Route translated or passthrough request
Provider->>Provider: Set auth headers
Provider->>Provider: Apply provider defaults
Provider->>Provider: Apply custom_upstream_headers
Provider->>Ctx: Read passthrough headers and strip set
Provider->>Upstream: Send composed upstream request
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant Server as Echo middleware
participant Ctx as Request context
participant Provider as CompatibleProvider
participant Upstream

Client->>Server: HTTP request with headers
Server->>Ctx: RequestSnapshotCapture stores snapshot/user path
alt passthrough_user_headers enabled
    Server->>Ctx: PassthroughHeaderCapture stores filtered headers
end
Server->>Ctx: TaggingCapture stores labels and do_not_pass strip set
Server->>Provider: Route translated or passthrough request
Provider->>Provider: Set auth headers
Provider->>Provider: Apply provider defaults
Provider->>Provider: Apply custom_upstream_headers
Provider->>Ctx: Read passthrough headers and strip set
Provider->>Upstream: Send composed upstream request
Loading

Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/custom-ups..." | Re-trigger Greptile

Comment thread internal/providers/headers.go Outdated
Comment thread internal/server/request_snapshot.go Outdated
Comment thread internal/providers/headers.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
internal/providers/config.go (1)

580-590: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Fail-closed fix looks correct; consider logging unrecognized values.

The explicit true-set plus default → false resolves the earlier fail-open concern. One residual gap: unrecognized values (e.g. "tru", "enalbed") are now silently coerced to false, so a fat-fingered value that was meant to enable passthrough disappears with no operator feedback. A warn log would surface the misconfiguration without changing the safe default.

🪵 Optional: warn on unrecognized boolean env values
 	default:
+		slog.Warn("unrecognized boolean env value; defaulting to false", "value", value)
 		b := false
 		return &b
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/config.go` around lines 580 - 590, The boolean parsing in
the config helper now fails closed, but unrecognized inputs are silently coerced
to false. Update the parsing logic around the boolean env/value helper in
config.go to emit a warning when the normalized value does not match the known
true/false sets, while still returning false by default. Keep the existing
behavior in the parsing function that handles trimmed values, and use the
existing logger/config context so operators can spot typos like “tru” or
“enalbed” without changing the safe default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@internal/providers/config.go`:
- Around line 580-590: The boolean parsing in the config helper now fails
closed, but unrecognized inputs are silently coerced to false. Update the
parsing logic around the boolean env/value helper in config.go to emit a warning
when the normalized value does not match the known true/false sets, while still
returning false by default. Keep the existing behavior in the parsing function
that handles trimmed values, and use the existing logger/config context so
operators can spot typos like “tru” or “enalbed” without changing the safe
default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07bb5caf-ed5e-40b4-ba9f-e6253215baa2

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd787e and 9f46b21.

📒 Files selected for processing (6)
  • internal/providers/config.go
  • internal/providers/headers.go
  • internal/providers/init.go
  • internal/providers/kimicode/kimicode_headers_test.go
  • internal/providers/xai/xai_test.go
  • internal/server/http.go

weselben added 2 commits July 8, 2026 19:42
…eader overrides

After resolving the earlier CodeRabbit review items, Greptile flagged two
maintainability issues in the header override implementation that would leave
misleading docs and dead parameters in the PR:

- `internal/providers/headers.go`: `shouldForward` had a stale docstring that
described the opposite forwarding behavior and a dead `case "only"` branch.
`"only"` is rejected at config validation time, so it was unreachable. The
function is now simplified to an if-chain and the docstring accurately
reflects the default-open behavior.

- `internal/server/request_snapshot.go`: `RequestSnapshotCapture` still carried
a `capturePassthroughHeaders` bool and a conditional block that captured
passthrough headers. On ingress-managed paths this value is always
overwritten by the later `PassthroughHeaderCapture` middleware; on
non-ingress-managed paths the block is never reached. Removed the parameter and
block, updated every caller and test, and dropped the test that exercised the
removed code path.

All changes are within the existing PR's changed-file set and the package test
suite (`go test ./internal/providers/... ./internal/server/... ./config/...`)
passes.
@codecov-commenter

codecov-commenter commented Jul 8, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

weselben added 5 commits July 8, 2026 21:36
…r overrides coverage

- Extend config_test.go with coverage for parseBoolEnv, parseHeaderMapEnv, and collectProviderEnvValues header overrides

- Extend headers_test.go with coverage for PassthroughHeadersFromContext, sourceHasHeader, logStaticOverridden, and applyPassthroughHeaders edge cases

- Add unsuffixed, suffixed, and ambiguous-provider env overlay tests for header override fields

- Integrate all coverage into existing test files instead of creating standalone test files
… skip mode

- Add TestInit_PropagateInvalidHeaderOverridesSkipMode to verify buildProviderConfig error is returned through Init

- Covers the error return path added by header overrides in both init.go and config.go
- Add TestServer_RegistersPassthroughHeaderCaptureWhenEnabled to ensure

  PassthroughUserHeadersEnabled causes the ingress capture middleware to run

- Covers the server.New registration path added by the header overrides feature
- Add TestNew_AppliesHeaderOverrideWiring to verify SetUserPathHeader is called

- Verify AnyPassthroughUserHeaders propagates from provider init to app state

- Uses minimal config with SQLite temp file and disabled storage-backed features
@weselben

weselben commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…r overrides

- Add TestInitializeProviders_PassthroughUserHeadersNotSetOnFailedProvider to guard the CodeRabbit finding that the passthrough flag must only be set after provider creation succeeds.
- Add TestProviderEnvValues_RawConfig_PassthroughUserHeaders and TestProviderEnvValues_Empty_HeaderFields to cover the missing line and partial branch Codecov reported in internal/providers/config.go.
@weselben

weselben commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

internal/providers/init.go (1)> 219-272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the passthrough flag behind successful provider creation
anyPassthroughUserHeaders is set before factory.Create, so a config with passthrough_user_headers: true still turns on header capture even if that provider fails to initialize and never becomes active. Move the check after Create succeeds.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/init.go` around lines 219 - 272, The
passthrough-user-headers flag is being recorded before provider creation
succeeds, so failed providers can still enable header capture. In
initializeProviders, move the HeaderOverrides.PassthroughUserHeaders check to
after factory.Create returns successfully, and only then update
anyPassthroughUserHeaders for the provider that actually initialized.

The passthrough flag is already set after factory.Create succeeds in the current branch, so a failed provider cannot enable header capture. I added a regression test to keep it that way and updated additional tests for config.go to have broader test coverage.

Commit: a7af32cfd0a798dadd0edb514bab59cad641d3a3

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '190,280p' internal/providers/init.go

Length of output: 3092


Confirmed — the factory.Create call happens on line before the err != nil check, which uses continue to skip failed providers, and the anyPassthroughUserHeaders flag is only set afterward, on the success path. So a failed provider indeed cannot set the flag. Thanks for the fix and the regression test.

✅ Action performed

Reviews resumed.

@weselben weselben marked this pull request as draft July 9, 2026 01:10
@weselben

weselben commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Converting to draft to fix a bug with the OpenRouter implementation. It currently overwrites headers even when 'User Headers Passthrough' is enabled.

Note: Other providers' implementations are working perfectly based on my final manual E2E tests.

weselben added 3 commits July 9, 2026 15:16
…header hierarchy

- Rewrite the custom-headers page to match the concise style of other
  docs/features/*.mdx files.
- Add a four-layer header hierarchy table: auth → provider defaults →
  static custom headers → passthrough user headers.
- Document provider default headers using OpenRouter's HTTP-Referer and
  X-OpenRouter-Title as the concrete example.
- Explain that skip lists filter caller headers only and do not strip
  provider defaults, and how to override defaults with custom_upstream_headers
  or passthrough headers.
@weselben weselben marked this pull request as ready for review July 9, 2026 17:26
Comment on lines +206 to +211
// Tagging "do not pass" headers are treated as blocked (Santiago #2).
if stripSet != nil {
if _, blocked := stripSet[lower]; blocked {
return false
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Normalize strip keys
TaggingStripHeadersFromContext stores canonical header names such as X-Team, but this lookup uses the lower-cased request header (x-team). When a tagging rule marks X-Team as do_not_pass, shouldForward misses the strip entry and forwards that header upstream, defeating the configured strip policy.

Context Used: CLAUDE.md (source)

Artifacts

Repro: focused Go test source demonstrating canonical tagging strip bypass

  • Contains supporting evidence from the run (text/x-go; charset=utf-8).

Repro: verbose go test output showing X-Team forwarded despite strip policy

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b4c4337

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.env.template (1)

12-15: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

"Same header layer" claim contradicts the four-layer hierarchy.

The comment says identity headers from OPENROUTER_SITE_URL/OPENROUTER_APP_NAME are "in the same header layer as custom_upstream_headers." However, per the CompatibleProviderConfig.DefaultHeaders field comment and the four-layer precedence table in docs/features/custom-headers.mdx (lines 55-65), DefaultHeaders are layer 2 (provider defaults) while custom_upstream_headers are layer 3 (static overrides). They are in different layers, which is why static overrides take precedence. The precedence statement itself is correct, but "same header layer" is internally inconsistent with it and misleading.

📝 Suggested wording fix
 # OpenRouter-specific: OPENROUTER_SITE_URL and OPENROUTER_APP_NAME also set
 # identity headers (HTTP-Referer and X-OpenRouter-Title) upstream. These are
-# in the same header layer as custom_upstream_headers, so static config values
-# and caller passthrough headers take precedence over these env vars.
+# in the provider-default header layer (below custom_upstream_headers), so
+# static config values and caller passthrough headers take precedence over
+# these env vars.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.template around lines 12 - 15, Update the OpenRouter comment in
.env.template so it no longer says the identity headers are “in the same header
layer as custom_upstream_headers.” Align the wording with the four-layer
precedence model used by CompatibleProviderConfig.DefaultHeaders and
custom_upstream_headers: make clear that OPENROUTER_SITE_URL and
OPENROUTER_APP_NAME map to provider default headers, while
custom_upstream_headers are a higher-precedence override layer, and keep the
existing precedence explanation consistent with that distinction.
config/providers.go (1)

26-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider documenting the new header-override fields.

CustomUpstreamHeaders, PassthroughUserHeaders, PassthroughUserHeadersSkip, and PassthroughUserHeadersSkipMode have no doc comments, unlike APIKeys above them. A short comment (e.g., valid skip/allow values for SkipMode, default behavior) would help operators reading this struct directly, mirroring the existing APIKeys convention.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/providers.go` around lines 26 - 29, The new header-override fields in
the provider config struct are undocumented, unlike the existing APIKeys field,
so add concise doc comments for CustomUpstreamHeaders, PassthroughUserHeaders,
PassthroughUserHeadersSkip, and PassthroughUserHeadersSkipMode. Describe the
intended behavior and defaults, especially how passthrough is enabled/disabled,
what the skip list applies to, and the valid SkipMode values such as allow and
skip, so readers can understand the ProviderConfig fields directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Line 136: The provider header env var docs in CLAUDE.md omit the
always-blocked header behavior, so add a short callout near the existing
CUSTOM_UPSTREAM_HEADERS/PASSTHROUGH_* description that certain credentials,
hop-by-hop/transport headers, and the user-path alias are never forwarded even
when configured. Reference the existing Custom Headers doc and make it explicit
that values like Authorization may be silently dropped by the blocklist so
operators know where to look.

In `@docs/features/custom-headers.mdx`:
- Around line 109-119: Update the OpenRouter scenario table in
custom-headers.mdx to reflect the default values returned by identityHeaders()
when one env var is unset. The current `—` entries are incorrect; in the `Only
OPENROUTER_SITE_URL set` row, `X-OpenRouter-Title` should show the default app
name, and in the `Only OPENROUTER_APP_NAME set` row, `HTTP-Referer` should show
the default site URL. Keep the rest of the `custom_upstream_headers` and
`passthrough_user_headers_skip` examples unchanged.

In `@internal/providers/headers_test.go`:
- Around line 776-794: The test case in HeaderOverridesConfig is not proving
that defaults are preserved when static headers do not override them, because
X-Default is redundantly set in both DefaultHeaders and CustomUpstreamHeaders.
Update the "static overrides defaults" case in
internal/providers/headers_test.go so X-Default is supplied only by
DefaultHeaders and verify the expected result still includes it, using the same
HeaderOverridesConfig and want map assertions.

In `@internal/providers/openai/compatible_provider.go`:
- Around line 121-138: Add a unit test covering the branch in buildHeaderMutator
where cfg.HeaderOverrides.DefaultHeaders is already populated so the guard
len(overrides.DefaultHeaders) == 0 prevents cfg.DefaultHeaders from overwriting
operator-supplied values. Exercise this through NewCompatibleProvider or
buildHeaderMutator in internal/providers/openai/compatible_provider.go, and
assert the resulting request headers preserve the explicit
HeaderOverrides.DefaultHeaders precedence described by the doc comment.
- Around line 96-100: The comment about per-request key rotation is stale in the
provider setup and is attached to the wrong assignment block. Move that note to
the key-selection logic in buildHeaderMutator, where keys.Next() actually
happens, or remove/reword it from the cfg.HeaderOverrides and cfg.UserPathAlias
section in compatible_provider.go so the documentation matches the code path.

---

Outside diff comments:
In @.env.template:
- Around line 12-15: Update the OpenRouter comment in .env.template so it no
longer says the identity headers are “in the same header layer as
custom_upstream_headers.” Align the wording with the four-layer precedence model
used by CompatibleProviderConfig.DefaultHeaders and custom_upstream_headers:
make clear that OPENROUTER_SITE_URL and OPENROUTER_APP_NAME map to provider
default headers, while custom_upstream_headers are a higher-precedence override
layer, and keep the existing precedence explanation consistent with that
distinction.

In `@config/providers.go`:
- Around line 26-29: The new header-override fields in the provider config
struct are undocumented, unlike the existing APIKeys field, so add concise doc
comments for CustomUpstreamHeaders, PassthroughUserHeaders,
PassthroughUserHeadersSkip, and PassthroughUserHeadersSkipMode. Describe the
intended behavior and defaults, especially how passthrough is enabled/disabled,
what the skip list applies to, and the valid SkipMode values such as allow and
skip, so readers can understand the ProviderConfig fields directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f8dfac1e-7239-424e-bd89-ed9aaa23d29f

📥 Commits

Reviewing files that changed from the base of the PR and between a7af32c and 5ad1fcd.

📒 Files selected for processing (19)
  • .env.template
  • CLAUDE.md
  • config/config.example.yaml
  • config/providers.go
  • docs/features/custom-headers.mdx
  • internal/providers/config.go
  • internal/providers/factory.go
  • internal/providers/headers.go
  • internal/providers/headers_test.go
  • internal/providers/keyring_config_test.go
  • internal/providers/ollama/ollama.go
  • internal/providers/ollama/ollama_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openrouter/openrouter.go
  • internal/providers/openrouter/openrouter_test.go
  • internal/providers/vllm/vllm.go
  • internal/providers/xai/xai_test.go
  • internal/server/http_test.go
  • run/lifecycle_test.go

Comment thread CLAUDE.md
Comment thread docs/features/custom-headers.mdx
Comment thread internal/providers/headers_test.go
Comment thread internal/providers/openai/compatible_provider.go Outdated
Comment thread internal/providers/openai/compatible_provider.go
weselben added 2 commits July 9, 2026 19:53
- Add test coverage for buildHeaderMutator DefaultHeaders promotion
- Add test asserting operator-supplied DefaultHeaders replace provider defaults
- Fix headers_test.go static-overrides-defaults case to prove defaults survive
- Move stale per-request key rotation comment into buildHeaderMutator
- Add blocked-header callout to CLAUDE.md provider header env var docs
- Correct OpenRouter scenario defaults in custom-headers.mdx
- Clarify OpenRouter default header layer in .env.template
- Add doc comments for header override fields in config/providers.go
…ultHeaders precedence

shouldForward lower-cased the header name before looking it up in the
tagging strip set, but TaggingStripHeadersFromContext stores canonical
names (e.g. "X-Team"). This caused do-not-pass tagging headers to be
forwarded upstream. Look up the canonical form instead, and update the
existing test to use canonical strip keys.

Also add a direct unit test for buildHeaderMutator ensuring that when
HeaderOverrides.DefaultHeaders is already populated, provider DefaultHeaders
do not overwrite operator-supplied values.

Refs: ENTERPILOT#513 (comment)
Refs: CodeRabbit review comment on internal/providers/openai/compatible_provider.go:121-138
@weselben

weselben commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Actionable comments posted: 5

[!CAUTION]
Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.env.template (1)

12-15: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

"Same header layer" claim contradicts the four-layer hierarchy.

The comment says identity headers from OPENROUTER_SITE_URL/OPENROUTER_APP_NAME are "in the same header layer as custom_upstream_headers." However, per the CompatibleProviderConfig.DefaultHeaders field comment and the four-layer precedence table in docs/features/custom-headers.mdx (lines 55-65), DefaultHeaders are layer 2 (provider defaults) while custom_upstream_headers are layer 3 (static overrides). They are in different layers, which is why static overrides take precedence. The precedence statement itself is correct, but "same header layer" is internally inconsistent with it and misleading.

📝 Suggested wording fix
 # OpenRouter-specific: OPENROUTER_SITE_URL and OPENROUTER_APP_NAME also set
 # identity headers (HTTP-Referer and X-OpenRouter-Title) upstream. These are
-# in the same header layer as custom_upstream_headers, so static config values
-# and caller passthrough headers take precedence over these env vars.
+# in the provider-default header layer (below custom_upstream_headers), so
+# static config values and caller passthrough headers take precedence over
+# these env vars.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.template around lines 12 - 15, Update the OpenRouter comment in
.env.template so it no longer says the identity headers are “in the same header
layer as custom_upstream_headers.” Align the wording with the four-layer
precedence model used by CompatibleProviderConfig.DefaultHeaders and
custom_upstream_headers: make clear that OPENROUTER_SITE_URL and
OPENROUTER_APP_NAME map to provider default headers, while
custom_upstream_headers are a higher-precedence override layer, and keep the
existing precedence explanation consistent with that distinction.
config/providers.go (1)

26-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider documenting the new header-override fields.

CustomUpstreamHeaders, PassthroughUserHeaders, PassthroughUserHeadersSkip, and PassthroughUserHeadersSkipMode have no doc comments, unlike APIKeys above them. A short comment (e.g., valid skip/allow values for SkipMode, default behavior) would help operators reading this struct directly, mirroring the existing APIKeys convention.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/providers.go` around lines 26 - 29, The new header-override fields in
the provider config struct are undocumented, unlike the existing APIKeys field,
so add concise doc comments for CustomUpstreamHeaders, PassthroughUserHeaders,
PassthroughUserHeadersSkip, and PassthroughUserHeadersSkipMode. Describe the
intended behavior and defaults, especially how passthrough is enabled/disabled,
what the skip list applies to, and the valid SkipMode values such as allow and
skip, so readers can understand the ProviderConfig fields directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Line 136: The provider header env var docs in CLAUDE.md omit the
always-blocked header behavior, so add a short callout near the existing
CUSTOM_UPSTREAM_HEADERS/PASSTHROUGH_* description that certain credentials,
hop-by-hop/transport headers, and the user-path alias are never forwarded even
when configured. Reference the existing Custom Headers doc and make it explicit
that values like Authorization may be silently dropped by the blocklist so
operators know where to look.

In `@docs/features/custom-headers.mdx`:
- Around line 109-119: Update the OpenRouter scenario table in
custom-headers.mdx to reflect the default values returned by identityHeaders()
when one env var is unset. The current `—` entries are incorrect; in the `Only
OPENROUTER_SITE_URL set` row, `X-OpenRouter-Title` should show the default app
name, and in the `Only OPENROUTER_APP_NAME set` row, `HTTP-Referer` should show
the default site URL. Keep the rest of the `custom_upstream_headers` and
`passthrough_user_headers_skip` examples unchanged.

In `@internal/providers/headers_test.go`:
- Around line 776-794: The test case in HeaderOverridesConfig is not proving
that defaults are preserved when static headers do not override them, because
X-Default is redundantly set in both DefaultHeaders and CustomUpstreamHeaders.
Update the "static overrides defaults" case in
internal/providers/headers_test.go so X-Default is supplied only by
DefaultHeaders and verify the expected result still includes it, using the same
HeaderOverridesConfig and want map assertions.

In `@internal/providers/openai/compatible_provider.go`:
- Around line 121-138: Add a unit test covering the branch in buildHeaderMutator
where cfg.HeaderOverrides.DefaultHeaders is already populated so the guard
len(overrides.DefaultHeaders) == 0 prevents cfg.DefaultHeaders from overwriting
operator-supplied values. Exercise this through NewCompatibleProvider or
buildHeaderMutator in internal/providers/openai/compatible_provider.go, and
assert the resulting request headers preserve the explicit
HeaderOverrides.DefaultHeaders precedence described by the doc comment.
- Around line 96-100: The comment about per-request key rotation is stale in the
provider setup and is attached to the wrong assignment block. Move that note to
the key-selection logic in buildHeaderMutator, where keys.Next() actually
happens, or remove/reword it from the cfg.HeaderOverrides and cfg.UserPathAlias
section in compatible_provider.go so the documentation matches the code path.

---

Outside diff comments:
In @.env.template:
- Around line 12-15: Update the OpenRouter comment in .env.template so it no
longer says the identity headers are “in the same header layer as
custom_upstream_headers.” Align the wording with the four-layer precedence model
used by CompatibleProviderConfig.DefaultHeaders and custom_upstream_headers:
make clear that OPENROUTER_SITE_URL and OPENROUTER_APP_NAME map to provider
default headers, while custom_upstream_headers are a higher-precedence override
layer, and keep the existing precedence explanation consistent with that
distinction.

In `@config/providers.go`:
- Around line 26-29: The new header-override fields in the provider config
struct are undocumented, unlike the existing APIKeys field, so add concise doc
comments for CustomUpstreamHeaders, PassthroughUserHeaders,
PassthroughUserHeadersSkip, and PassthroughUserHeadersSkipMode. Describe the
intended behavior and defaults, especially how passthrough is enabled/disabled,
what the skip list applies to, and the valid SkipMode values such as allow and
skip, so readers can understand the ProviderConfig fields directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f8dfac1e-7239-424e-bd89-ed9aaa23d29f

📥 Commits

Reviewing files that changed from the base of the PR and between a7af32c and 5ad1fcd.

📒 Files selected for processing (19)
  • .env.template
  • CLAUDE.md
  • config/config.example.yaml
  • config/providers.go
  • docs/features/custom-headers.mdx
  • internal/providers/config.go
  • internal/providers/factory.go
  • internal/providers/headers.go
  • internal/providers/headers_test.go
  • internal/providers/keyring_config_test.go
  • internal/providers/ollama/ollama.go
  • internal/providers/ollama/ollama_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openrouter/openrouter.go
  • internal/providers/openrouter/openrouter_test.go
  • internal/providers/vllm/vllm.go
  • internal/providers/xai/xai_test.go
  • internal/server/http_test.go
  • run/lifecycle_test.go

Both should be fixed @coderabbitai

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.

2 participants