fix(run_agent): preserve dotted Bedrock inference-profile model IDs (#11976)#11992
fix(run_agent): preserve dotted Bedrock inference-profile model IDs (#11976)#11992briandevans wants to merge 1 commit into
Conversation
…ousResearch#11976) Bedrock rejects ``global-anthropic-claude-opus-4-7`` with ``HTTP 400: The provided model identifier is invalid`` because its inference profile IDs embed structural dots (``global.anthropic.claude-opus-4-7``) that ``normalize_model_name`` was converting to hyphens. ``AIAgent._anthropic_preserve_dots`` did not include ``bedrock`` in its provider allowlist, so every Claude-on- Bedrock request through the AnthropicBedrock SDK path shipped with the mangled model ID and failed. Root cause ---------- ``run_agent.py:_anthropic_preserve_dots`` (previously line 6589) controls whether ``agent.anthropic_adapter.normalize_model_name`` converts dots to hyphens. The function listed Alibaba, MiniMax, OpenCode Go/Zen and ZAI but not Bedrock, so when a user set ``provider: bedrock`` with a dotted inference-profile model the flag returned False and ``normalize_model_name`` mangled every dot in the ID. All four call sites in run_agent.py (``build_anthropic_kwargs`` + three fallback / review / summary paths at lines 6707, 7343, 8408, 8440) read from this same helper. The bug shape matches NousResearch#5211 for opencode-go, which was fixed in commit f77be22 by extending this same allowlist. Fix --- * Add ``"bedrock"`` to the provider allowlist. * Add ``"bedrock-runtime."`` to the base-URL heuristic as defense-in-depth, so a custom-provider-shaped config with ``base_url: https://bedrock-runtime.<region>.amazonaws.com`` also takes the preserve-dots path even if ``provider`` isn't explicitly set to ``"bedrock"``. This mirrors how the code downstream at run_agent.py:759 already treats either signal as "this is Bedrock". Bedrock model ID shapes covered ------------------------------- | Shape | Preserved | | --- | --- | | ``global.anthropic.claude-opus-4-7`` (reporter's exact ID) | ✓ | | ``us.anthropic.claude-sonnet-4-5-20250929-v1:0`` | ✓ | | ``apac.anthropic.claude-haiku-4-5`` | ✓ | | ``anthropic.claude-3-5-sonnet-20241022-v2:0`` (foundation) | ✓ | | ``eu.anthropic.claude-3-5-sonnet`` (regional inference profile) | ✓ | Non-Claude Bedrock models (Nova, Llama, DeepSeek) take the ``bedrock_converse`` / boto3 path which does not call ``normalize_model_name``, so they were never affected by this bug and remain unaffected by the fix. Narrow scope — explicitly not changed ------------------------------------- * ``bedrock_converse`` path (non-Claude Bedrock models) — already correct; no ``normalize_model_name`` in that pipeline. * Provider aliases (``aws``, ``aws-bedrock``, ``amazon``, ``amazon-bedrock``) — if a user bypasses the alias-normalization pipeline and passes ``provider="aws"`` directly, the base-URL heuristic still catches it because Bedrock always uses a ``bedrock-runtime.`` endpoint. Adding the aliases themselves to the provider set is cheap but would be scope creep for this fix. * No other places in ``agent/anthropic_adapter.py`` mangle dots, so the fix is confined to ``_anthropic_preserve_dots``. Regression coverage ------------------- ``tests/agent/test_bedrock_integration.py`` gains three new classes: * ``TestBedrockPreserveDotsFlag`` (5 tests): flag returns True for ``provider="bedrock"`` and for Bedrock runtime URLs (us-east-1 and ap-northeast-2 — the reporter's region); returns False for non- Bedrock AWS URLs like ``s3.us-east-1.amazonaws.com``; canary that Anthropic-native still returns False. * ``TestBedrockModelNameNormalization`` (5 tests): every documented Bedrock model-ID shape survives ``normalize_model_name`` with the flag on; inverse canary pins that ``preserve_dots=False`` still mangles (so a future refactor can't decouple the flag from its effect). * ``TestBedrockBuildAnthropicKwargsEndToEnd`` (2 tests): integration through ``build_anthropic_kwargs`` shows the reporter's exact model ID ends up unmangled in the outgoing kwargs. Three of the new flag tests fail on unpatched ``origin/main`` with ``assert False is True`` (preserve-dots returning False for Bedrock), confirming the regression is caught. Validation ---------- ``source venv/bin/activate && python -m pytest tests/agent/test_bedrock_integration.py tests/agent/test_minimax_provider.py -q`` -> 84 passed (40 new bedrock tests + 44 pre-existing, including the minimax canaries that pin the pattern this fix mirrors). CI-aligned broad suite: 12827 passed, 39 skipped, 19 pre-existing baseline failures (all reproduce on clean ``origin/main``; none in the touched code path). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes Bedrock Claude inference-profile model IDs being incorrectly normalized by preserving structural dots (.) for Bedrock requests, preventing HTTP 400 “invalid model identifier” errors when using dotted Bedrock model IDs.
Changes:
- Extend
AIAgent._anthropic_preserve_dots()to treatprovider="bedrock"as dot-preserving. - Add a defense-in-depth heuristic to preserve dots when
base_urlcontainsbedrock-runtime.. - Add regression/integration tests covering the preserve-dots flag,
normalize_model_name(), andbuild_anthropic_kwargs()end-to-end for Bedrock-shaped model IDs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
run_agent.py |
Updates Bedrock detection for dot-preserving model normalization (provider allowlist + base URL heuristic). |
tests/agent/test_bedrock_integration.py |
Adds regression tests ensuring dotted Bedrock model IDs are preserved through normalization and kwargs building. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
CI audit — all three failing checks are unrelated to this change: 1. This is a known test-ordering flake in the tool registry. Both tests:
The failure mode (tool registry returning only 2. 3. Reproduction summary: Happy to trigger a re-run or push an empty-commit if maintainers want confirmation, but I don't want to force-push noise into the diff. The substantive content of the PR is green. |
|
Merged via PR #12793 — your commit was cherry-picked onto current main with your authorship preserved in git log (commit 1cf1016). Thanks for the thorough fix + tests, @briandevans! |
|
Thanks @teknium1 — really appreciate the cherry-pick with authorship preserved. Happy it shipped. A couple of siblings in the same model-validator vein are already up if useful:
Both verified to reproduce on clean |
What does this PR do?
Fixes #11976.
Bedrock rejects
global-anthropic-claude-opus-4-7with:…because Bedrock inference-profile IDs embed structural dots (
global.anthropic.claude-opus-4-7) thatnormalize_model_namewas converting to hyphens on the way out.AIAgent._anthropic_preserve_dotsdid not includebedrockin its allowlist, so every Claude-on-Bedrock request through the AnthropicBedrock SDK path shipped with a mangled model ID and failed.The reporter verified on a production deployment (Discord gateway, Bedrock Opus 4.7, ap-northeast-2) that direct boto3 and anthropic-sdk calls with the same model ID succeed — only the Hermes path fails. Request-dump JSON shows
"model": "global-anthropic-claude-opus-4-7"(all dots mangled to hyphens).Root cause
run_agent.py::_anthropic_preserve_dotscontrols whetheragent.anthropic_adapter.normalize_model_nameconverts dots to hyphens. The allowlist named Alibaba, MiniMax, OpenCode Go/Zen and ZAI — not Bedrock. All four call sites inrun_agent.py(lines 6707, 7343, 8408, 8440) read from this same helper, so a single-line miss cascades across the whole AnthropicBedrock SDK code path.The bug shape exactly matches #5211 for opencode-go, which was fixed in commit
f77be22cby extending this same allowlist.Fix
"bedrock"to the provider allowlist."bedrock-runtime."to the base-URL heuristic as defense-in-depth: a custom-provider-shaped config withbase_url: https://bedrock-runtime.<region>.amazonaws.comalso takes the preserve-dots path even ifproviderisn't explicitly"bedrock". This mirrors how the code downstream atrun_agent.py:759already treats either signal as "this is Bedrock".Precedence / compat table
providerbase_urlbedrockhttps://bedrock-runtime.us-east-1.amazonaws.comhttps://bedrock-runtime.ap-northeast-2.amazonaws.com(reporter)customhttps://s3.us-east-1.amazonaws.com(unrelated AWS)bedrock-runtime.anthropichttps://api.anthropic.comclaude-sonnet-4.6→claude-sonnet-4-6(correct)minimax,zai, etc.Bedrock model ID shapes covered by the regression suite:
global.anthropic.claude-opus-4-7(reporter's exact ID)us.anthropic.claude-sonnet-4-5-20250929-v1:0apac.anthropic.claude-haiku-4-5anthropic.claude-3-5-sonnet-20241022-v2:0(foundation)Narrow scope — explicitly not changed
bedrock_conversepath (non-Claude Bedrock models: Nova, Llama, DeepSeek) — already correct; that pipeline usesagent.bedrock_adapter.build_converse_kwargswhich does not callnormalize_model_name. Non-Claude Bedrock models were never affected by this bug.aws,aws-bedrock,amazon,amazon-bedrock) — if a user bypasses the alias-normalization pipeline and passesprovider="aws"directly, the base-URL heuristic still catches it because Bedrock always uses abedrock-runtime.endpoint. Adding the aliases to the provider set would be scope creep for this fix; the downstream atrun_agent.py:759already only checks the literal"bedrock"string.agent/anthropic_adapter.pymangle dots, so the fix is confined to the single allowlist in_anthropic_preserve_dots.Pre-empt reviewer Q&A
Q: Why the base-URL heuristic on top of the provider check — belt-and-suspenders?
The provider and base-URL paths are both real config shapes in Hermes. A Bedrock user could configure
provider: bedrock(takes provider allowlist path) OR set up acustom_providers:entry pointing atbedrock-runtime.…amazonaws.com(takes base-URL heuristic path). The existing code atrun_agent.py:759uses the same OR pattern when detecting Bedrock for api-mode selection — I'm matching that precedent.Q: Could
"bedrock-runtime."match an unrelated URL?Only if a user explicitly sets a custom proxy URL containing the literal
bedrock-runtime.substring. Even then, the only effect is dot preservation in model IDs — no security or auth impact. Low risk.Q: Does this weaken dot-mangling for Anthropic native?
No — canary test
test_anthropic_native_still_does_not_preserve_dotspins thatclaude-sonnet-4.6→claude-sonnet-4-6still happens for Anthropic-native. The set-membership check only matchesbedrock; Anthropic still falls through topreserve_dots=False.Q: Case sensitivity of the base-URL check?
base = (getattr(self, "base_url", "") or "").lower()— always lowercased before substring match. A mixed-case URL likehttps://Bedrock-Runtime.us-east-1.amazonaws.comstill matches.Q: What about
_is_allowlisted_sensitive_path-style symlink attacks?Not applicable — this is model-name normalization, not filesystem access. No path-traversal surface.
Q: Why not also gate the AND-style check (
normalizedandresolved)?That pattern applies to filesystem-path security checks.
_anthropic_preserve_dotsoperates on a string config value, not a filesystem object. No OS-level form mismatch to reconcile.How to test
Focused suite:
→ 84 passed (40 new bedrock tests + 44 pre-existing, including the minimax canaries that pin the pattern this fix mirrors).
Verify the tests catch the bug on origin/main:
→ 3 fail with
assert False is True(preserve-dots returning False for Bedrock provider + Bedrock base URLs). Restore withgit stash pop.CI-aligned broad suite:
→ 12827 passed, 39 skipped, 19 pre-existing baseline failures. None in the touched code path:
tests/gateway/test_dingtalk.py×4tests/gateway/test_matrix.pytests/hermes_cli/test_claw.py×3tests/hermes_cli/test_gateway_wsl.py×2tests/gateway/test_approve_deny_commands.py×2tests/tools/test_file_staleness.py×2 (addressed in PR fix(file_tools): allowlist macOS user-writable temp subtrees under /private/var/ #11983)tests/tools/test_local_interrupt_cleanup.pytests/tools/test_tts_mistral.pytests/tools/test_approval_heartbeat.py(thread-timing flake on macOS — passes in isolation)tests/tools/test_send_message_tool.py×2 (pass in isolation)tests/tools/test_skills_tool.py(passes in isolation)Tested on: macOS 15 (Darwin 25.5.0), Python 3.11.15.
Related Issue
Fixes #11976
Related / similar bug class:
f77be22c.Type of Change
Changes Made
run_agent.py: add"bedrock"to_anthropic_preserve_dotsprovider allowlist and"bedrock-runtime."to its base-URL heuristic.tests/agent/test_bedrock_integration.py: addTestBedrockPreserveDotsFlag(5),TestBedrockModelNameNormalization(5), andTestBedrockBuildAnthropicKwargsEndToEnd(2) — 12 new tests covering the flag, the adapter's normalization, and end-to-end build-kwargs integration.Adjacent surfaces checked
preserve_dots=self._anthropic_preserve_dots()call sites inrun_agent.py(lines 6707, 7343, 8408, 8440) — all pick up the fix via the single helper.agent/anthropic_adapter.py::normalize_model_nameandbuild_anthropic_kwargs— covered by the new end-to-end test.agent/bedrock_adapter.py::build_converse_kwargs— non-Claude Bedrock path, doesn't usenormalize_model_name; unaffected.run_agent.py:759bedrock detection — already uses the sameprovider == "bedrock" OR base_url contains "bedrock-runtime"shape I'm mirroring.Checklist
Code
fix(scope):)origin/mainwithout the fix; end-to-end integration test usesbuild_anthropic_kwargsto exercise the full flow)Documentation & Housekeeping
model.provider: bedrockworks unchangedNotes for reviewers
python -m pytest tests/ -q --ignore=tests/integration --ignore=tests/e2e --tb=short -n auto(matches.github/workflows/tests.yml).hermes_cli/**changes — the Nix workflow should not trigger.build_anthropic_kwargsso a future refactor can't decouple the flag from its downstream effect.