GitHub
03/10/2026, 2:16 AMresolve_schema_references (in singer_sdk/singerlib/schema.py) recursively resolves $ref values inside properties, patternProperties, items, anyOf, allOf, and oneOf — but not additionalProperties. Any $ref nested under additionalProperties is left unresolved in the returned schema.
When OpenAPISchema.fetch_schema is used and a component schema contains additionalProperties: { "$ref": "..." }, the resulting schema is not self-contained. Downstream validators (e.g. jsonschema) then fail with PointerToNowhere because the $ref points into #/components/schemas/… which no longer exists in the extracted standalone schema.
Steps to reproduce
Given an OpenAPI spec with a component like:
"WorkspaceResource": {
"type": "object",
"properties": {
"_links": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/Link"
}
}
}
}
Fetching the schema via OpenAPISchema.fetch_schema("WorkspaceResource") returns:
{
"type": "object",
"properties": {
"_links": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/Link" # ← still unresolved
}
}
}
}
Validating a real record against this schema with jsonschema then raises:
referencing.exceptions.PointerToNowhere: '/components/schemas/Link' does not exist within {...}
This manifests in practice when running the SDK's own built-in tap tests (get_tap_test_class) against a tap that uses OpenAPISchema — test_tap_stream_record_matches_stream_schema fails for every stream whose resource schema has additionalProperties: { "$ref": "..." }.
Root cause
_resolve_schema_references in singer_sdk/singerlib/schema.py defines a _SchemaKey class and recurses only into the keywords listed there:
class _SchemaKey:
ref = "$ref"
items = "items"
properties = "properties"
pattern_properties = "patternProperties"
any_of = "anyOf"
all_of = "allOf"
one_of = "oneOf"
additionalProperties is absent. The JSON Schema specification also allows $ref to appear in not, `if`/`then`/`else`, prefixItems, contains, propertyNames, and `unevaluatedProperties`/`unevaluatedItems`, which are similarly unhandled — but additionalProperties is by far the most common in real-world OpenAPI specs.
Expected behaviour
resolve_schema_references should recurse into additionalProperties (and ideally the other missing applicator keywords) so the returned schema is fully self-contained.
Proposed fix
Add additionalProperties to `_resolve_schema_references`:
if "additionalProperties" in schema and isinstance(schema["additionalProperties"], dict):
schema["additionalProperties"] = _resolve_schema_references(
schema["additionalProperties"],
resolver,
visited_refs,
)
A more complete fix would also cover not, if, then, else, prefixItems, contains, propertyNames, unevaluatedProperties, and unevaluatedItems.
Environment
• Singer SDK version: 0.53.5
• Python version: 3.14.3
• Reproduced with a tap using OpenAPISchema + StreamSchema against a Spring HATEOAS API whose resource schemas use additionalProperties: { "$ref": "..." } for HAL link maps.
meltano/sdkGitHub
03/10/2026, 2:46 AM