ComposioHQ/composio · error · ValueError
Dynamic-key schema `$ref` must be a string
Error message
Dynamic-key schema `$ref` must be a string
What it means
While recursively checking dynamic-key schemas, _check_dynamic_references found a "$ref" whose value is not a string (e.g. a dict, list, or number). Per JSON Schema, $ref must be a string URI/reference, so the converter rejects the schema at construction time with ValueError.
Source
Thrown at python/composio/utils/schema_converter.py:615
"""Reject external, anchored, and unresolved references before validation.
Only schema positions are walked. ``const``, ``default``, ``enum``, and
``examples`` hold instance data, so a ``$ref``-shaped value stored there is
a payload rather than a reference and must not block tool wrapping.
"""
if checked_references is None:
checked_references = set()
if isinstance(schema, list):
for item in schema:
_check_dynamic_references(item, root_schema, checked_references)
return
if not isinstance(schema, dict):
return
reference = schema.get("$ref")
if reference is not None:
if not isinstance(reference, str):
raise ValueError("Dynamic-key schema `$ref` must be a string")
resolved = _resolve_local_json_pointer(reference, root_schema)
if not isinstance(resolved, (dict, bool)):
raise ValueError(
f"Dynamic-key schema reference {reference!r} does not target a schema"
)
if reference not in checked_references:
checked_references.add(reference)
_check_dynamic_references(resolved, root_schema, checked_references)
for keyword, value in schema.items():
if keyword in _SCHEMA_VALUED_KEYWORDS or keyword in _SCHEMA_LIST_KEYWORDS:
_check_dynamic_references(value, root_schema, checked_references)
elif keyword in _SCHEMA_MAP_KEYWORDS:
if isinstance(value, dict):
for entry in value.values():
_check_dynamic_references(entry, root_schema, checked_references)
elif keyword == "items":
# A schema, or a list of schemas for tuple validation.View on GitHub (pinned to 64b1b85502)
Solutions
- Find the non-string $ref in the tool schema and replace its value with the correct string pointer (e.g. "#/$defs/thing") — usually the object next to it is the intended target and should live under definitions
- Validate the schema with a JSON Schema meta-validator before feeding it to the SDK
- Fix the generator/template that produced the malformed ref
Example fix
# before
{"$ref": {"type": "string"}}
# after
{"$ref": "#/$defs/target"} Defensive patterns
Strategy: validation
Validate before calling
def refs_are_strings(schema):
if isinstance(schema, dict):
if "$ref" in schema and not isinstance(schema["$ref"], str):
return False
return all(refs_are_strings(v) for v in schema.values())
if isinstance(schema, list):
return all(refs_are_strings(i) for i in schema)
return True Try / catch
try:
build_model(schema)
except ValueError as e:
if "$ref` must be a string" in str(e):
schema = fix_malformed_refs(schema) Prevention
- Lint generated schemas for non-string $ref values
- Validate against the JSON Schema metaschema before conversion
- Avoid hand-building refs via string concatenation of unknown types
When it happens
Trigger: A schema node contains "$ref": {"type": "string"} (a nested schema accidentally used as the ref value) or "$ref": 123 — typically an authoring or transformation bug where the intended schema was placed under the wrong key.
Common situations: Machine-generated schemas from template bugs; YAML-to-JSON conversions that misplace nodes; prompt-generated schemas from LLMs; refactoring that wrapped the target schema instead of referencing it.
Related errors
- Dynamic-key schema reference {reference!r} does not target a
- Cannot resolve $ref {pointer}
- Dynamic-key schema reference {reference!r} must be a local J
- Unresolvable dynamic-key schema reference: {reference!r}
- Unsupported $ref pointer: ${pointer}
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/eff04544a5f08ed6.
Report an issue: GitHub.