Summary
transform_schema (the structured-output schema transformer used by messages.parse / beta.messages.parse) crashes with an AssertionError on any schema that uses the list form of type — e.g. {"type": ["string", "null"]} — which is valid JSON Schema and explicitly supported by the Anthropic structured-outputs API.
The failure happens at request-build time, before any API call.
Repro
from anthropic import transform_schema
# Valid JSON Schema; also documented as supported by the structured-outputs API
transform_schema({"type": ["string", "null"]})
One-liner:
python -c 'from anthropic import transform_schema; transform_schema({"type": ["string", "null"]})'
Actual (on anthropic==0.112.0; the relevant code is unchanged on current main of src/anthropic/lib/_parse/_transform.py):
AssertionError: Expected code to be unreachable, but got: ['string', 'null']
A more realistic nested variant fails the same way:
transform_schema({
"type": "object",
"properties": {"name": {"type": ["string", "null"]}},
"required": ["name"],
"additionalProperties": False,
})
Expected
An equivalent transformed schema, e.g. rewriting the type union into anyOf branches:
{"anyOf": [{"type": "string"}, {"type": "null"}]}
Why this should be supported
- JSON Schema allows
type to be "a string or an array of strings" — the list form is standard, not an extension.
- The structured outputs docs explicitly document type arrays as supported (and meter them): "Total parameters that use
anyOf or type arrays (for example, "type": ["string", "null"]) across all strict schemas." So the API accepts this shape; only the SDK-side transformer rejects it.
- Nullable-type unions are pervasive in real schemas: Pydantic
Optional[...] fields in older/alternative generators, OpenAPI 3.1 output, GraphQL-derived schemas, and MCP tool inputSchemas commonly emit "type": [..., "null"].
Root cause
In src/anthropic/lib/_parse/_transform.py, type_ is typed and handled as a scalar:
type_: Optional[SupportedTypes] = json_schema.pop("type", None)
...
elif type_ == "boolean" or type_ == "integer" or type_ == "number" or type_ == "null" or type_ is None:
pass
else:
assert_never(type_)
A list value matches none of the scalar branches and falls into assert_never.
Suggested fix
When type_ is a list, canonicalize it before the scalar handling, e.g. rewrite {"type": [t1, t2, ...], **rest} into an anyOf of single-type branches (with sibling constraints attached to the non-null branches) and recurse. Single-element lists can collapse to the scalar form.
Related (not duplicates)
Environment
anthropic==0.112.0 (also reproduces against main's _transform.py)
- Python 3.12.13, macOS arm64
Summary
transform_schema(the structured-output schema transformer used bymessages.parse/beta.messages.parse) crashes with anAssertionErroron any schema that uses the list form oftype— e.g.{"type": ["string", "null"]}— which is valid JSON Schema and explicitly supported by the Anthropic structured-outputs API.The failure happens at request-build time, before any API call.
Repro
One-liner:
python -c 'from anthropic import transform_schema; transform_schema({"type": ["string", "null"]})'Actual (on
anthropic==0.112.0; the relevant code is unchanged on currentmainofsrc/anthropic/lib/_parse/_transform.py):A more realistic nested variant fails the same way:
Expected
An equivalent transformed schema, e.g. rewriting the type union into
anyOfbranches:{"anyOf": [{"type": "string"}, {"type": "null"}]}Why this should be supported
typeto be "a string or an array of strings" — the list form is standard, not an extension.anyOfor type arrays (for example,"type": ["string", "null"]) across all strict schemas." So the API accepts this shape; only the SDK-side transformer rejects it.Optional[...]fields in older/alternative generators, OpenAPI 3.1 output, GraphQL-derived schemas, and MCP toolinputSchemas commonly emit"type": [..., "null"].Root cause
In
src/anthropic/lib/_parse/_transform.py,type_is typed and handled as a scalar:A list value matches none of the scalar branches and falls into
assert_never.Suggested fix
When
type_is a list, canonicalize it before the scalar handling, e.g. rewrite{"type": [t1, t2, ...], **rest}into ananyOfof single-type branches (with sibling constraints attached to the non-nullbranches) and recurse. Single-element lists can collapse to the scalar form.Related (not duplicates)
{}sub-schemas (ValueErrorpath) — a different gap in the same function.Environment
anthropic==0.112.0(also reproduces againstmain's_transform.py)