Skip to content

fix(foundry): don't treat an env-derived resource as conflicting with base_url#1743

Open
sean-kim05 wants to merge 1 commit into
anthropics:mainfrom
sean-kim05:fix/foundry-copy-with-resource-env
Open

fix(foundry): don't treat an env-derived resource as conflicting with base_url#1743
sean-kim05 wants to merge 1 commit into
anthropics:mainfrom
sean-kim05:fix/foundry-copy-with-resource-env

Conversation

@sean-kim05

Copy link
Copy Markdown

Summary

AnthropicFoundry.copy() / with_options() (and their async equivalents) raise

ValueError: base_url and resource are mutually exclusive

whenever ANTHROPIC_FOUNDRY_RESOURCE is set in the environment.

Root cause

copy() reconstructs the client and always forwards base_url (derived from the live client) while never forwarding resource:

return self.__class__(
    ...
    base_url=str(base_url or self.base_url),   # always non-None
    ...                                         # resource is never passed
)

__init__ then falls back to the env var for resource:

resource = resource if resource is not None else os.environ.get("ANTHROPIC_FOUNDRY_RESOURCE")
base_url = base_url if base_url is not None else os.environ.get("ANTHROPIC_FOUNDRY_BASE_URL")

if base_url is None:
    ...
elif resource is not None:
    raise ValueError("base_url and resource are mutually exclusive")

So when ANTHROPIC_FOUNDRY_RESOURCE is set, the forwarded base_url and the env-derived resource are both non-None and the guard fires — even though the caller never supplied a conflicting configuration. Any deployment that configures Foundry purely through env vars (ANTHROPIC_FOUNDRY_RESOURCE + ANTHROPIC_FOUNDRY_API_KEY) and then calls the public copy() / with_options() API hits this.

Reproduction

os.environ["ANTHROPIC_FOUNDRY_RESOURCE"] = "example-resource"
os.environ["ANTHROPIC_FOUNDRY_API_KEY"] = "sk-foundry-abc"
client = AnthropicFoundry()   # OK
client.copy()                 # ValueError: base_url and resource are mutually exclusive

Fix

An env-derived resource is only a fallback for building base_url, so it must not conflict with a base_url that was actually supplied. Track whether resource was passed explicitly and only raise on a genuine caller-supplied conflict; otherwise a supplied base_url wins over the resource env var (matching normal explicit-arg-over-env precedence). Applied symmetrically to the sync and async clients.

Explicitly passing both base_url and resource is still rejected (and remains a static type error via the existing overloads).

Testing

Added to tests/lib/test_azure.py:

  • test_copy_with_resource_from_environment (sync + async) — copy() / with_options() succeed with ANTHROPIC_FOUNDRY_RESOURCE set (fails before this change).
  • test_explicit_base_url_and_resource_conflict — the runtime guard still rejects an explicit base_url + resource pair.

Verified RED → GREEN. Full tests/lib/test_azure.py passes (21 tests); ./scripts/lint clean (ruff + pyright + mypy).

… base_url

`AnthropicFoundry.copy()` / `with_options()` (and the async equivalents) raised
`ValueError: base_url and resource are mutually exclusive` whenever
`ANTHROPIC_FOUNDRY_RESOURCE` was set in the environment.

`copy()` always forwards `base_url` (derived from the live client) and never
forwards `resource`. In `__init__`, `resource` then falls back to
`ANTHROPIC_FOUNDRY_RESOURCE`, so with that env var set both `base_url` and
`resource` end up non-None and the mutual-exclusion guard fires — even though
the caller never asked for a conflicting configuration. Any deployment that
configures Foundry purely through env vars and calls the public `copy()` /
`with_options()` API hit this.

An env-derived `resource` is only a fallback for building `base_url`, so it
should not conflict with a `base_url` that was supplied. Track whether
`resource` was passed explicitly and only raise on a genuine caller-supplied
conflict; a supplied `base_url` otherwise wins over the resource env var.
Applied to both the sync and async clients.
@sean-kim05 sean-kim05 requested a review from a team as a code owner July 7, 2026 05:00
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.

1 participant