{"record":{"id":"a0e15352b22c5136","repo":"openai/openai-python","slug":"expected-ref-ref-to-resolved-to-a-dictionary","errorCode":null,"errorMessage":"Expected `$ref: {ref}` to resolved to a dictionary but got {resolved}","messagePattern":"Expected `\\$ref: (.+?)` to resolved to a dictionary but got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/openai/lib/_pydantic.py","lineNumber":106,"sourceCode":"\n    # strip `None` defaults as there's no meaningful distinction here\n    # the schema will still be `nullable` and the model will default\n    # to using `None` anyway\n    if json_schema.get(\"default\", NOT_GIVEN) is None:\n        json_schema.pop(\"default\")\n\n    # we can't use `$ref`s if there are also other properties defined, e.g.\n    # `{\"$ref\": \"...\", \"description\": \"my description\"}`\n    #\n    # so we unravel the ref\n    # `{\"type\": \"string\", \"description\": \"my description\"}`\n    ref = json_schema.get(\"$ref\")\n    if ref and has_more_than_n_keys(json_schema, 1):\n        assert isinstance(ref, str), f\"Received non-string $ref - {ref}\"\n\n        resolved = resolve_ref(root=root, ref=ref)\n        if not is_dict(resolved):\n            raise ValueError(f\"Expected `$ref: {ref}` to resolved to a dictionary but got {resolved}\")\n\n        # properties from the json schema take priority over the ones on the `$ref`\n        json_schema.update({**resolved, **json_schema})\n        json_schema.pop(\"$ref\")\n        # Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,\n        # we call `_ensure_strict_json_schema` again to fix the inlined schema and ensure it's valid.\n        return _ensure_strict_json_schema(json_schema, path=path, root=root)\n\n    return json_schema\n\n\ndef resolve_ref(*, root: dict[str, object], ref: str) -> object:\n    if not ref.startswith(\"#/\"):\n        raise ValueError(f\"Unexpected $ref format {ref!r}; Does not start with #/\")\n\n    path = ref[2:].split(\"/\")\n    resolved = root\n    for key in path:","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/openai/openai-python/blob/9917c6e28e66e90e1227b3d223c06a8c5441515a/src/openai/lib/_pydantic.py#L88-L124","documentation":"While inlining $ref entries for strict mode, the resolved target looked up in the root schema was not a dictionary. This indicates a structurally malformed or self-inconsistent JSON schema, not user input error per se.","triggerScenarios":"A model whose schema has a $ref pointing at a non-object location (e.g. refs into arrays or scalar positions), or schemas mutated/corrupted between generation and strictification.","commonSituations":"Custom CoreSchema manipulation; pydantic version quirks generating unusual refs; nested generics with older pydantic 2.x versions.","solutions":["Upgrade pydantic to the latest v2 patch","Simplify nested generic models that trigger unusual $ref layouts","Print model_json_schema() and verify every $ref resolves to an object node under #/definitions or #/$defs"],"exampleFix":"# before\nclass Inner(BaseModel): ...\nclass Outer(BaseModel):\n    inner: Inner | list[Inner] | int  # complex union producing odd refs\n# after\nclass Outer(BaseModel):\n    inners: list[Inner]","handlingStrategy":"validation","validationCode":"schema = Output.model_json_schema()\ndef refs_resolve(node, root):\n    if isinstance(node, dict):\n        if \"$ref\" in node and not isinstance(node.get(\"$ref\"), str):\n            return False\n        return all(refs_resolve(v, root) for v in node.values())\n    return not isinstance(node, (list,)) or all(refs_resolve(v, root) for v in node)\nassert refs_resolve(schema, schema)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid deeply nested generics/unions of models","Pin recent pydantic v2 versions","Add a schema-generation smoke test per output model"],"tags":["json-schema","refs","pydantic","strict-mode"],"backgroundTag":"schema-validation-failed","analyzedSha":"9917c6e28e66e90e1227b3d223c06a8c5441515a","analyzedAt":"2026-08-28T11:46:34.183Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}