Skip to content

fix(auxiliary): honor /anthropic-suffixed gateway base_url on aux + fallback calls - #62061

Closed
iso2kx wants to merge 1 commit into
NousResearch:mainfrom
iso2kx:fix/anthropic-compatible-host-suffix
Closed

fix(auxiliary): honor /anthropic-suffixed gateway base_url on aux + fallback calls#62061
iso2kx wants to merge 1 commit into
NousResearch:mainfrom
iso2kx:fix/anthropic-compatible-host-suffix

Conversation

@iso2kx

@iso2kx iso2kx commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@iso2kx
iso2kx force-pushed the fix/anthropic-compatible-host-suffix branch from beea758 to dbe2daf Compare July 10, 2026 12:33
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jul 10, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: auxiliary-client custom-provider base_url routing family — #36757 (post-override base_url for endpoint detection), #24586/#34299 (ProviderProfile.api_mode consult), #17027 (merged, Anthropic Messages transport auto-detect). Same agent/auxiliary_client.py area, distinct mechanism (this preserves the /anthropic suffix on aux+fallback calls).

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused regression fix. Current main still has the reported gap: agent/auxiliary_client.py:824-832 accepts only api.anthropic.com, and _try_anthropic() consequently discards a configured gateway URL at agent/auxiliary_client.py:2639-2643. The proposed /anthropic and /anthropic/v1 predicate matches the primary resolver's established routing convention in hermes_cli/runtime_provider.py:128-130 and the existing auxiliary transport detector in agent/auxiliary_client.py:1476-1483.

The added config-backed regression test exercises the affected _try_anthropic() construction path while retaining the existing non-Anthropic-host guard. The PR is behind current main, but the only current target-file change after its base is unrelated at agent/auxiliary_client.py:4711-4714, so this appears mechanically salvageable.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 11, 2026
…allback calls

`_try_anthropic()` applies the configured `model.base_url` only when
`_is_anthropic_compatible_host()` trusts it, but that check accepted only the
literal `api.anthropic.com` host. Anthropic-compatible gateways that expose the
native Messages protocol under a `/anthropic` path suffix (MiniMax, Zhipu GLM,
LiteLLM-style relays, self-hosted proxies) were rejected, so every auxiliary
call (title generation, memory extraction, vision, reflection) and the
`provider: anthropic` fallback chain discarded the configured base_url and fell
back to `https://api.anthropic.com`. That diverges from the primary path, which
already trusts the `/anthropic` suffix via
`runtime_provider._detect_api_mode_for_url`, and fails outright when the gateway
(not Anthropic) holds the credentials.

Accept `/anthropic` and `/anthropic/v1` suffixed URLs in
`_is_anthropic_compatible_host()`, matching the primary-path convention and
`_wrap_if_needed`. A bare non-Anthropic base_url (e.g. `openrouter.ai/api/v1`
left on `provider: anthropic`) still returns False, preserving the NousResearch#52608 guard.
@iso2kx
iso2kx force-pushed the fix/anthropic-compatible-host-suffix branch from dbe2daf to 48c8f28 Compare July 25, 2026 21:26
@iso2kx

iso2kx commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main — now ahead 1, behind 0, and the target hunk applied cleanly (the _is_anthropic_compatible_host body is unchanged on main since this branch's original base).

Bumping this because the gap bit me again in production this morning, which I think makes the P3 a little optimistic.

Setup: provider: anthropic with model.base_url pointing at a local Anthropic-compatible gateway under an /anthropic suffix — the exact convention runtime_provider._detect_api_mode_for_url already routes on. The primary path honors it; aux and fallback don't.

What happened:

  1. Primary ran fine through the gateway — 9 consecutive claude-opus-5 calls.
  2. Primary hit a 429, so the chain did its job: Fallback activated: claude-opus-5 → claude-opus-4-8 (anthropic).
  3. The fallback went to base_url=https://api.anthropic.comnot the gateway — despite fallback_providers[0].base_url being explicitly set to the gateway URL.
  4. Auth lives on the gateway, not at Anthropic, so the request came back 400 and the session was dead.

The relevant log line, verbatim:

agent.conversation_loop: API call failed (attempt 2/3) provider=anthropic
  base_url=https://api.anthropic.com model=claude-opus-4-8 summary=HTTP 400

Confirmed the traffic genuinely never reached the gateway (its own request counter didn't move for those calls), so this isn't a mislabelled log line. A second tell from the same window: during a concurrent DNS outage the primary leg returned 502 getaddrinfo ENOTFOUND api.anthropic.com — loopback reachable, upstream dead — while the fallback leg returned a client-side APIConnectionError: Connection error., which is only possible if that client was dialling an external host.

Worth noting the blast radius is wider than the fallback chain: because _try_anthropic() is the shared chokepoint, every provider: anthropic auxiliary call (compression, title generation, vision) diverges the same way. Those fail quietly rather than surfacing a 400, so the fallback case is just the loud one.

Two smaller notes for whoever picks this up:

  • The failure mode is specifically silent divergence — the main agent and its side channels talk to different endpoints, and nothing logs the disagreement. That's what made it take a while to find both times.
  • I'd previously carried this as a local patch to the vendored auxiliary_client.py; a routine update overwrote it and the regression came straight back. That's not an argument for the patch, just for landing it upstream rather than leaving people to re-apply it.

Happy to add a WARNING when _try_anthropic() discards a configured model.base_url, if that'd be a useful companion to the predicate change — it would have turned both of these incidents into a one-line grep.

@alt-glitch alt-glitch added P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API area/config Config system, migrations, profiles and removed P3 Low — cosmetic, nice to have sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 25, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #85533 with your commit cherry-picked onto current main — authorship preserved in git log. The path-suffix trust now matches the primary path's convention while keeping the #52608 bare-host rejection, exactly as your tests pinned. Thanks for the clean, well-scoped fix.

@teknium1 teknium1 closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles 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 sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants