You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Python SDK fails with RecursionError when importing types that contain deeply recursive discriminated unions. This is triggered by the Fern regeneration on branch fern-bot/2026-03-30T11-49Z (PR #752), which added 4 new arithmetic operator node types (add, sub, mul, div) to the ConvAI AST expression system. The CI run shows 90 type files failing with RecursionError.
Root Cause
The ConvAI agent workflow system models expressions as an AST (Abstract Syntax Tree). Every operator node type (and, or, equals, greater_than, etc.) has a children / left / right field whose type is a discriminated union that can be any other operator node type — including itself. This creates a fully connected graph of cross-references.
Before PR #752: ~10 operator types → ~100 cross-file references → Python handles it.
After PR #752: ~14 operator types → ~196 cross-file references → RecursionError at import time.
The problem is structural: Fern generates each discriminated union variant set as a separate file, which creates circular import chains. Python's forward reference resolution (update_forward_refs / model_rebuild) hits the recursion limit when the import graph becomes too deep.
Failing types (sample):
adhoc_agent_config_override_for_test_request_model RecursionError: maximum recursion depth exceeded while calling a Python object
agent_workflow_request_model RecursionError: maximum recursion depth exceeded while calling a Python object
ast_addition_operator_node_input RecursionError: maximum recursion depth exceeded in __instancecheck__
ast_and_operator_node_input RecursionError: maximum recursion depth exceeded while calling a Python object
ast_conditional_operator_node_input_condition RecursionError: maximum recursion depth exceeded
workflow_edge_model_input RecursionError: maximum recursion depth exceeded while calling a Python object
workflow_expression_condition_model_input RecursionError: maximum recursion depth exceeded while calling a Python object
# ... 83 more
Prior Art
This exact problem was hit in October 2025. A workaround was developed in PR #658 on branch fern-support/fix-recursion-error (commits 7114c16, 9af0c47) but never merged because it lacked a sustainable mechanism.
The workaround consolidated all mutually recursive variant classes into two large files:
Each individual *_children_item.py / *_left.py / *_right.py file was replaced with a thin re-export stub. The trick: putting all definitions in one file with from __future__ import annotations at the top means Python never evaluates type annotations at class-definition time, breaking the cycle. update_forward_refs() then resolves everything at the end.
This workaround was not added to .fernignore, so the next regeneration wiped it out. It also quickly became stale as the OpenAPI schema evolved.
WorkflowExpressionConditionModelInput/Output — expression field
Proposed Fix (for ElevenLabs to apply manually)
Apply the consolidated file approach from PR #658, sustainably:
Create updated consolidated files that include all current variants (including ConditionalOperator added since Oct 2025) plus the 4 new arithmetic operators:
Replace individual stub files — all *_children_item.py, *_left.py, *_right.py, and the new arithmetic operator files — with thin re-exports from the consolidated file.
Update tests/test_recursive_models.py to instantiate the new arithmetic operator types.
Proposed Fix (for Fern to implement upstream)
The sustainable fix is for Fern's Python generator to detect mutually recursive discriminated union type graphs and consolidate them into a single module automatically, rather than generating one file per type. Specifically:
Cycle detection: During codegen, detect strongly connected components (SCCs) in the type reference graph.
Module consolidation: For each SCC with more than one node, emit all types in that SCC into a single Python module instead of separate files.
Re-export stubs: Generate thin re-export files at the expected paths so that existing imports continue to work.
from __future__ import annotations: Ensure the consolidated module uses deferred annotation evaluation (PEP 563) so class bodies with forward references are never evaluated eagerly.
This is the same pattern used by tools like datamodel-code-generator and hand-written Pydantic models for recursive schemas.
Repro
git clone https://github.com/elevenlabs/elevenlabs-python
cd elevenlabs-python
git checkout fern-bot/2026-03-30T11-49Z
poetry install
poetry run python -c "from elevenlabs.types import AstAndOperatorNodeInput"# RecursionError: maximum recursion depth exceeded while calling a Python object
Summary
The Python SDK fails with
RecursionErrorwhen importing types that contain deeply recursive discriminated unions. This is triggered by the Fern regeneration on branchfern-bot/2026-03-30T11-49Z(PR #752), which added 4 new arithmetic operator node types (add,sub,mul,div) to the ConvAI AST expression system. The CI run shows 90 type files failing withRecursionError.Root Cause
The ConvAI agent workflow system models expressions as an AST (Abstract Syntax Tree). Every operator node type (
and,or,equals,greater_than, etc.) has achildren/left/rightfield whose type is a discriminated union that can be any other operator node type — including itself. This creates a fully connected graph of cross-references.Before PR #752: ~10 operator types → ~100 cross-file references → Python handles it.
After PR #752: ~14 operator types → ~196 cross-file references →
RecursionErrorat import time.The problem is structural: Fern generates each discriminated union variant set as a separate file, which creates circular import chains. Python's forward reference resolution (
update_forward_refs/model_rebuild) hits the recursion limit when the import graph becomes too deep.Failing types (sample):
Prior Art
This exact problem was hit in October 2025. A workaround was developed in PR #658 on branch
fern-support/fix-recursion-error(commits7114c16,9af0c47) but never merged because it lacked a sustainable mechanism.The workaround consolidated all mutually recursive variant classes into two large files:
src/elevenlabs/types/ast_operators_input_consolidated.py(~4000 lines)src/elevenlabs/types/ast_operators_output_consolidated.py(~4000 lines)Each individual
*_children_item.py/*_left.py/*_right.pyfile was replaced with a thin re-export stub. The trick: putting all definitions in one file withfrom __future__ import annotationsat the top means Python never evaluates type annotations at class-definition time, breaking the cycle.update_forward_refs()then resolves everything at the end.This workaround was not added to
.fernignore, so the next regeneration wiped it out. It also quickly became stale as the OpenAPI schema evolved.Affected Types (PR #752)
The following type families are involved in the recursive graph and need special handling:
AST operator node union types (each has
children,left, orrightfields that can be any operator):AstAndOperatorNodeInput/Output—childrenfieldAstOrOperatorNodeInput/Output—childrenfieldAstEqualsOperatorNodeInput/Output—left/rightfieldsAstGreaterThanOperatorNodeInput/Output—left/rightfieldsAstGreaterThanOrEqualsOperatorNodeInput/Output—left/rightfieldsAstLessThanOperatorNodeInput/Output—left/rightfieldsAstLessThanOrEqualsOperatorNodeInput/Output—left/rightfieldsAstNotEqualsOperatorNodeInput/Output—left/rightfieldsAstConditionalOperatorNodeInput/Output—condition/trueExpression/falseExpressionfieldsAstAdditionOperatorNodeInput/Output—left/rightfieldsAstSubtractionOperatorNodeInput/Output—left/rightfieldsAstMultiplicationOperatorNodeInput/Output—left/rightfieldsAstDivisionOperatorNodeInput/Output—left/rightfieldsWorkflow condition types (hold expression fields):
WorkflowEdgeModelInput/Output—forwardCondition/backwardConditionfieldsWorkflowExpressionConditionModelInput/Output—expressionfieldProposed Fix (for ElevenLabs to apply manually)
Apply the consolidated file approach from PR #658, sustainably:
Create updated consolidated files that include all current variants (including
ConditionalOperatoradded since Oct 2025) plus the 4 new arithmetic operators:src/elevenlabs/types/ast_operators_input_consolidated.pysrc/elevenlabs/types/ast_operators_output_consolidated.pyReplace individual stub files — all
*_children_item.py,*_left.py,*_right.py, and the new arithmetic operator files — with thin re-exports from the consolidated file.Add all modified/new files to
.fernignoreso future regenerations don't overwrite them. This is the critical step that was missing from PR fix(types): refactored type definitions to fix infinite recursion error #658.Update
tests/test_recursive_models.pyto instantiate the new arithmetic operator types.Proposed Fix (for Fern to implement upstream)
The sustainable fix is for Fern's Python generator to detect mutually recursive discriminated union type graphs and consolidate them into a single module automatically, rather than generating one file per type. Specifically:
from __future__ import annotations: Ensure the consolidated module uses deferred annotation evaluation (PEP 563) so class bodies with forward references are never evaluated eagerly.This is the same pattern used by tools like
datamodel-code-generatorand hand-written Pydantic models for recursive schemas.Repro
References
fern-support/fix-recursion-error(commits7114c16,9af0c47)