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

  1. 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
  2. Validate the schema with a JSON Schema meta-validator before feeding it to the SDK
  3. 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

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


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/eff04544a5f08ed6. Report an issue: GitHub.