feat: forward inbound User-Agent to HF router (append convention)

#5
by pierric HF Staff - opened

Problem

The proxy's outbound calls to the HF router set no User-Agent (reqwest 0.12 has no default UA โ€” seanmonstar/reqwest#540). So the router records metadata.userAgent = "Amazon CloudFront" for all proxied Claude Code traffic and cannot attribute it to the originating client. This blocks accurate per-agent/per-client stats in the HF router (e.g. distinguishing Claude Code from other OpenAI-spec router clients).

Root cause

  • Claude Code does send a User-Agent (Anthropic SDK / CLAUDE_AGENT_SDK_CLIENT_APP โ†’ claude-code/<ver>, plus anthropic-version, x-stainless-*).
  • The single CloudFront distribution (E1YEKOUYDG655W) fronting router.huggingface.co forwards viewer headers to the proxy origin via its allViewer origin-request policy (NoCookie, f4a73954-โ€ฆ). So the inbound UA is available to the proxy โ€” it just wasn't reading or forwarding it.
  • Verified empirically: a probe with User-Agent: probe-ua-distinctive-12345 reached the router's metadata.userAgent unchanged. When no UA is sent, CloudFront injects Amazon CloudFront (its default for absent UA). CloudFront does not rewrite a present UA under allViewer โ€” the Amazon CloudFront entries are purely the proxy sending no UA.

Fix

Read the inbound User-Agent and forward it appended to the proxy's own identity, following the standard reverse-proxy convention (e.g. Grafana's forward_user_agent):

User-Agent: claude-code-adapter/<ver> <inbound-client-ua>

Falls back to the proxy identity alone when no inbound UA is present. Implemented as a shared forwarded_user_agent(&headers) helper in src/handlers/mod.rs, applied at all three outbound request sites:

  • src/handlers/messages.rs โ€” initial request
  • src/handlers/messages.rs โ€” 400-retry request
  • src/handlers/responses.rs โ€” responses API request

The proxy's own identity uses env!("CARGO_PKG_NAME")/env!("CARGO_PKG_VERSION") so it stays in sync with Cargo.toml.

Effect

After this ships, the HF router records metadata.userAgent = "claude-code-adapter/0.1.0 claude-code/2.1.205" for Claude Code traffic instead of "Amazon CloudFront". No CloudFront config change, no router schema change โ€” reuses the router's existing metadata.userAgent capture path (server/app/inferencePartners.ts, req.header("user-agent")).

For the agent vs. classifier distinction (both carry the same Claude Code UA), the proxy's existing stop-sequence detection (src/adapter/convert_request.rs) remains the signal โ€” orthogonal, tracked separately.

Verification

  • cargo test green โ€” 16/16, including 3 new unit tests for the helper (forwards_inbound_user_agent_appended, falls_back_to_proxy_identity_when_no_inbound_ua, falls_back_when_inbound_ua_empty).
  • CloudFront allViewer UA forwarding confirmed by direct probe (distinctive UA reached router metadata.userAgent verbatim).

Note: Generated with Claude Code, acting for @Pierrci .

Investigation trace: anthropic-proxy-classifier-thinking-timeout-5dc27541.jsonl (HF internal bucket, 906KB) โ€” full session incl. the CloudFront UA-forwarding probes. Also on Traces: https://traces.com/s/jn7ewfqkrv48s1138011mmanps8bv22v

pierric changed pull request status to open
Ready to merge
This branch is ready to get merged automatically.

Sign up or log in to comment