Skip to content

fix(anthropic): override SDK User-Agent for third-party endpoints (#24293) - #24295

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/anthropic-third-party-cloudflare-ua-24293
Closed

fix(anthropic): override SDK User-Agent for third-party endpoints (#24293)#24295
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/anthropic-third-party-cloudflare-ua-24293

Conversation

@briandevans

@briandevans briandevans commented May 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Override the Anthropic Python SDK's default User-Agent: Anthropic/Python <ver> with User-Agent: hermes-agent/<version> whenever build_anthropic_client constructs a client for a third-party (non-anthropic.com) endpoint. Direct anthropic.com traffic is unchanged — the SDK's native UA is preserved there.

Self-hosted Anthropic-compatible proxies (e.g. GLM relays, custom gateways) commonly sit behind Cloudflare with bot-detection rules that 403 the Anthropic SDK fingerprint. Reproduced in #24293:

# Without SDK User-Agent → 200 OK
# With "User-Agent: Anthropic/Python 0.101.0" → 403 Forbidden

In agent/anthropic_adapter.py, the _is_third_party_anthropic_endpoint branch of build_anthropic_client only set anthropic-beta in default_headers; the SDK's default UA flowed through untouched, so every request from Hermes to such a proxy was rejected with HTTP 403: Your request was blocked. Pattern mirrors the existing OAuth (claude-cli/...) and Kimi-coding (claude-code/0.1.0) branches in the same function — both already override the SDK UA for routing reasons. Version resolution lazily imports hermes_cli.__version__ and caches it; falls back to "0.0.0" if unavailable (mirrors acp_adapter/server.py and gateway/platforms/yuanbao.py conventions).

Related Issue

Fixes #24293

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • agent/anthropic_adapter.py — in the third-party branch of build_anthropic_client, populate default_headers["User-Agent"] with hermes-agent/<resolved-version> before constructing the SDK client. Native-Anthropic branch untouched.
  • tests/agent/test_anthropic_adapter.py — new cases: test_third_party_endpoint_overrides_sdk_user_agent asserts UA starts with hermes-agent/ and Anthropic/Python never appears; test_native_anthropic_endpoint_does_not_override_user_agent asserts no UA override for direct anthropic.com calls. Updates test_custom_base_url to assert both anthropic-beta and the new User-Agent are present together.

How to Test

  1. uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/agent/test_anthropic_adapter.py::TestBuildAnthropicClient tests/agent/test_anthropic_adapter.py::TestBuildAnthropicKwargs -v
  2. Expected: 10 + 23 = 33 passed.
  3. Regression guard: revert the fix — both new assertions fail with KeyError: 'User-Agent'. Restore — all 10 TestBuildAnthropicClient cases pass.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run focused tests for the touched code and all pass (33/33)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — UA header is platform-independent
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Related / Positioning

  • Fixes Custom providers behind Cloudflare WAF return 403 due to SDK User-Agent #24293.
  • Mirrors the existing OAuth (claude-cli/...) and Kimi-coding (claude-code/0.1.0) UA-override branches in the same function.
  • The user-proposed custom_providers.user_agent config knob is deliberately not included here — the default-override pattern matches how the existing OAuth/Kimi branches handle SDK UA overrides and unblocks affected users without requiring per-provider config awareness. A config-knob follow-up can layer on top if maintainers want explicit per-provider control.

Sibling code paths that may need the same fix: the OpenAI client construction sites in agent/auxiliary_client.py (around 15 OpenAI(...) callsites) likely 403 in the same Cloudflare scenario when custom_providers use the OpenAI wire format. Intentionally left out of this PR's scope to keep the diff small — happy to widen if preferred.

Copilot AI review requested due to automatic review settings May 12, 2026 10:11

Copilot AI 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.

Pull request overview

This PR updates the Anthropic client construction logic so that when Hermes targets third-party (non-anthropic.com) Anthropic-compatible endpoints, it overrides the Anthropic Python SDK’s default User-Agent with a Hermes-specific hermes-agent/<version> UA to avoid Cloudflare/WAF blocks, while keeping native anthropic.com traffic unchanged.

Changes:

  • Add lazy Hermes version resolution and cache it for use in User-Agent headers.
  • Override User-Agent for third-party Anthropic endpoints in build_anthropic_client.
  • Extend and update unit tests to assert the new UA behavior for third-party endpoints and the absence of the override for native Anthropic.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
agent/anthropic_adapter.py Adds Hermes version resolver and sets default_headers["User-Agent"] for third-party endpoints while preserving native Anthropic behavior.
tests/agent/test_anthropic_adapter.py Updates existing coverage and adds regression tests to validate UA override behavior and ensure native Anthropic requests aren’t modified.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API labels May 12, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

CI audit — all test failures on this PR are pre-existing baselines on clean origin/main (dd0923b). Zero failures are in the touched files (agent/anthropic_adapter.py, tests/agent/test_anthropic_adapter.py).

Reproduced locally on dd0923bb8:

Test Symptom Cause on main
tests/agent/test_auxiliary_client.py::TestAuxiliaryPoolAwareness::test_try_nous_uses_pool_entry model == 'qwen/qwen3.6-plus' not 'google/gemini-3-flash-preview' aux-client default model changed; assertion stale
tests/run_agent/test_provider_parity.py::TestAuxiliaryClientProviderPriority::test_nous_when_no_openrouter same same
tests/gateway/test_config.py::TestStreamingConfig::test_from_dict_malformed_numeric_values_fall_back_to_defaults 0.8 != 1.0 edit_interval default changed
tests/gateway/test_tts_media_routing.py::test_streaming_delivery_routes_telegram_{flac,mp3,ogg}… (3) send_document/voice mock awaited 0 times media-routing dispatch changed
tests/gateway/test_update_streaming.py::TestUpdatePromptInterception::test_recognized_slash_command_bypasses_pending_update_prompt AttributeError: 'GatewayRunner' object has no attribute 'config' renamed/removed attribute
tests/gateway/test_verbose_command.py::TestVerboseCommand::{test_defaults_to_all_when_no_tool_progress_set, test_per_platform_isolation} text says ALL not VERBOSE; key is 'all' not 'verbose' verbose→all rename
tests/run_agent/test_async_httpx_del_neuter.py::TestClientCacheBoundedGrowth::test_same_key_replaces_stale_loop_entry 'old-model' != 'new-model' stale loop entry not replaced
tests/test_ctx_halving_fix.py::TestEphemeralMaxOutputTokens (4) AttributeError: 'AIAgent' object has no attribute '_use_long_lived_prefix_cache' renamed/removed attribute
tests/tools/test_vision_native_fast_path.py::test_vision_capable_main_model_uses_fast_path "No LLM provider configured for task=vision" CI env missing vision provider; passes locally
tests/hermes_cli/test_update_gateway_restart.py::TestFindGatewayPidsExclude (3) pids == [] psutil/procfs path differs in CI; passes on macOS
tests/hermes_cli/test_web_server.py::TestPluginAPIAuth::test_plugin_route_allows_auth 404 != 200 plugin route registration regression on main

None of these touch anthropic_adapter or third-party Anthropic endpoint behavior.

@briandevans
briandevans force-pushed the fix/anthropic-third-party-cloudflare-ua-24293 branch 2 times, most recently from cce2916 to 2ebba6e Compare May 19, 2026 00:31
@briandevans
briandevans force-pushed the fix/anthropic-third-party-cloudflare-ua-24293 branch 4 times, most recently from eddceed to 93160ac Compare May 29, 2026 18:13
@briandevans
briandevans force-pushed the fix/anthropic-third-party-cloudflare-ua-24293 branch 5 times, most recently from c1098da to 665dbe8 Compare June 1, 2026 12:14
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to focus the queue on security/file-safety work where civilian merges are landing. Happy to reopen if maintainers want this picked up.

@briandevans briandevans closed this Jun 2, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

Reopening — this is provider-SDK-drift hardening (third-party Anthropic-endpoint UA), which is a salvage niche worth keeping open. Apologies for the churn.

@briandevans briandevans reopened this Jun 2, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

Housekeeping: closing to keep my open-PR set focused on actively-reviewed work. This has been open ~20d without maintainer review and the surrounding code has continued to move, so it's unlikely to land as-is. The underlying fix still stands — happy to reopen and rebase if it would be useful. Thanks!

@briandevans briandevans closed this Jun 2, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

Reopening — closed this in error during queue housekeeping. This is in an active salvage niche (security / provider-SDK-drift / install-correctness / civilian-repro'd P1), not stale plumbing. Keeping it open.

@briandevans briandevans reopened this Jun 2, 2026
@briandevans
briandevans force-pushed the fix/anthropic-third-party-cloudflare-ua-24293 branch from 665dbe8 to ada252c Compare June 2, 2026 11:18
@briandevans
briandevans force-pushed the fix/anthropic-third-party-cloudflare-ua-24293 branch 6 times, most recently from c40d2ca to e44ce85 Compare June 4, 2026 01:14
…usResearch#24293)

Self-hosted Anthropic-compatible proxies (e.g. GLM relays, custom
Cloudflare-fronted gateways) commonly enable bot-detection rules that
403 the Anthropic Python SDK's default ``Anthropic/Python <ver>``
User-Agent. The user-reported workaround was to manually patch
``build_anthropic_client`` to send a non-SDK UA in the third-party
branch.

This change applies that workaround unconditionally for any
``_is_third_party_anthropic_endpoint`` request, setting
``User-Agent: hermes-agent/<version>``. The pattern mirrors the
existing OAuth (``claude-cli/...``) and Kimi-coding
(``claude-code/0.1.0``) branches in the same function, which already
override the SDK UA for routing reasons. Direct ``anthropic.com``
traffic is unaffected — those requests still ship with the SDK's
native UA, which Anthropic's infrastructure relies on for routing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to keep the queue focused — this has sat 47 days with no maintainer pickup. Happy to reopen if maintainers want the third-party-endpoint User-Agent override picked up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom providers behind Cloudflare WAF return 403 due to SDK User-Agent

3 participants