{"record":{"id":"62ac8e7d916f2e90","repo":"deepset-ai/haystack","slug":"refusing-to-deserialize-handle-it-resolves-to-62ac8e","errorCode":null,"errorMessage":"Refusing to deserialize '{handle}': it resolves to a builtin that is not a type and cannot be used as a type annotation or class reference. If you trust the source of this data, load it with unsafe=True to bypass deserialization safety checks.","messagePattern":"Refusing to deserialize '(.+?)': it resolves to a builtin that is not a type and cannot be used as a type annotation or class reference\\. If you trust the source of this data, load it with unsafe=True to bypass deserialization safety checks\\.","errorType":"exception","errorClass":"DeserializationError","httpStatus":null,"severity":"error","filePath":"haystack/core/serialization_security.py","lineNumber":543,"sourceCode":"\ndef _check_builtin_is_type(resolved: object, handle: str) -> None:\n    \"\"\"\n    Reject a `builtins` member resolved in a type/class context that is not a `type`.\n\n    Used by `deserialize_type` and `import_class_by_name`, which resolve type annotations and class\n    references — always classes. Requiring the resolved `builtins` member to be a `type` lets every\n    builtin type through (e.g. `str`, `memoryview`) while rejecting every builtin *function* (e.g.\n    `eval`, `exec`, `getattr`), with no denylist to maintain. Bypassed in `unsafe=True` mode.\n\n    :param resolved:\n        The object resolved from the serialized handle.\n    :param handle:\n        The original serialized handle, used only for the error message.\n    \"\"\"\n    if _is_unsafe_deserialization():\n        return\n    if not isinstance(resolved, type):\n        raise DeserializationError(\n            f\"Refusing to deserialize '{handle}': it resolves to a builtin that is not a type and \"\n            f\"cannot be used as a type annotation or class reference. If you trust the source of \"\n            f\"this data, load it with unsafe=True to bypass deserialization safety checks.\"\n        )\n\n\n@contextmanager\ndef _deserialization_context(allowed_modules: Iterable[str] | None = None, unsafe: bool = False) -> Iterator[None]:\n    \"\"\"\n    Context manager that activates a per-call deserialization context.\n\n    Patterns from `allowed_modules` are appended to the parent context's patterns, and `unsafe`\n    is OR-ed with the parent's `unsafe` flag — so this never narrows the active permissions.\n    The previous context is restored on exit.\n    \"\"\"\n    parent = _get_context()\n    extra = parent.extra_allowed + (tuple(allowed_modules) if allowed_modules else ())\n    merged_unsafe = parent.unsafe or unsafe","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/core/serialization_security.py#L525-L561","documentation":"A serialized type annotation handle resolved to a builtin that is not a class (type), e.g. 'len' or 'print'. Haystack only allows builtins that are actual types to be used as type annotations or class references during deserialization. This prevents invoking arbitrary non-type builtins from untrusted data.","triggerScenarios":"deserialize_type or _import_class_by_name resolving a handle like 'builtins.len' or 'print' where isinstance(resolved, type) is False, while unsafe mode is off.","commonSituations":"Typos in serialized type fields (e.g. 'dict' misspelled as 'dict_items' or 'open'); hand-written pipeline YAML annotations; data produced by buggy exporters.","solutions":["Correct the handle in the serialized data to a real type, e.g. 'builtins.dict' or a full class path","If the builtin is intentionally needed as data (not a type), restructure the payload so it is not deserialized as a type annotation","Load with unsafe=True if you trust the source"],"exampleFix":"// before (YAML)\ndata_type: \"builtins.len\"\n// after\ndata_type: \"builtins.str\"","handlingStrategy":"validation","validationCode":"import builtins\nimport importlib\ndef is_valid_type_handle(handle: str) -> bool:\n    try:\n        module, _, name = handle.rpartition(\".\")\n        obj = getattr(builtins if module == \"builtins\" else importlib.import_module(module), name)\n        return isinstance(obj, type)\n    except Exception:\n        return False","typeGuard":"import builtins\ndef resolves_to_type(handle: str) -> bool:\n    name = handle.split(\".\")[-1]\n    return isinstance(getattr(builtins, name, None), type)","tryCatchPattern":"from haystack.core.errors import DeserializationError\ntry:\n    pipe = Pipeline.loads(yaml_str)\nexcept DeserializationError as e:\n    if \"not a type\" in str(e):\n        print(\"Fix the type annotation handle in the serialized data:\", e)\n    else:\n        raise","preventionTips":["Only reference real classes (types) in type annotation fields","Autocomplete/verify builtin names like dict, list, str when editing YAML by hand","Regenerate serialized pipelines from code instead of editing type fields manually"],"tags":["serialization","security","type-annotation"],"backgroundTag":"invalid-type-annotation","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}