Comfy-Org / Comfy-Org/ComfyUI

ComfyNode.GET_SCHEMA() OUTPUT_IS_LIST cache may cause nested inherited ComfyNode classes get bad schema related to the initialization order

Open
#16,026 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
133k
Forks
15.7k
Avg merge
1d 7h
Merged PRs (30d)
158

Description

https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_api/latest/_io.py#L2216-L2263
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/execution.py#L322-L327

## Reproduce Steps

Create `ComfyUI/custom_nodes/ComfyUI-EmptyImage/__init__.py`

```python
import torch
import comfy.model_management
from comfy_api.latest import ComfyExtension, io

class EmptyImage1(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="EmptyImage1",
category="EmptyImage",
inputs=[
io.Int.Input("width", default=512, min=1, max=16384),
io.Int.Input("height", default=512, min=1, max=16384),
io.Int.Input("batch_size", default=1, min=1, max=4096),
io.Int.Input("color", default=0, min=0, max=0xFFFFFF),
],
outputs=[
io.Image.Output(),
],
)

@classmethod
def execute(cls, width, height, batch_size, color) -> io.NodeOutput:
dtype = comfy.model_management.intermediate_dtype()
device = comfy.model_management.intermediate_device()
r = torch.full([batch_size, height, width, 1], ((color >> 16) & 0xFF) / 0xFF, device=device, dtype=dtype)
g = torch.full([batch_size, height, width, 1], ((color >> 8) & 0xFF) / 0xFF, device=device, dtype=dtype)
b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF, device=device, dtype=dtype)
return io.NodeOutput(torch.cat((r, g, b), dim=-1))

class EmptyImage2(EmptyImage1):
@classmethod
def define_schema(cls):
schema = super().define_schema()
schema.node_id = "EmptyImage2"
schema.outputs[0].is_output_list = True
return schema

@classmethod
def execute(cls, width, height, batch_size, color) -> io.NodeOutput:
return io.NodeOutput([super().execute(width, height, batch_size, color)[0]])

class EmptyImageExtension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
EmptyImage1,
EmptyImage2,
]

async def comfy_entrypoint() -> EmptyImageExtension:
return EmptyImageExtension()

```

Image

If change the init order to

```python
class EmptyImageExtension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
EmptyImage2,
EmptyImage1,
]
```

it works.

## Simplified Code

```python
class A:
@classmethod
def define_schema(cls):
return {
'OUTPUT_IS_LIST': [False],
}

SCHEMA = None
@classmethod
def GET_SCHEMA(cls):
if cls.SCHEMA is None:
cls.SCHEMA = cls.define_schema()

class B(A):
@classmethod
def define_schema(cls):
schema = super().define_schema()
schema['OUTPUT_IS_LIST'] = [True]
return schema

A.GET_SCHEMA()
B.GET_SCHEMA()
print('A', A.SCHEMA)
print('B', B.SCHEMA)
```

Output:

```plain
A {'OUTPUT_IS_LIST': [False]}
B {'OUTPUT_IS_LIST': [False]}
```

If change the code to:

```python
B.GET_SCHEMA()
A.GET_SCHEMA()
print('A', A.SCHEMA)
print('B', B.SCHEMA)
```

got output:

```plain
A {'OUTPUT_IS_LIST': [False]}
B {'OUTPUT_IS_LIST': [True]}
```

## Workaround

Prevent any class inheritance

```python
class EmptyImage2(io.ComfyNode):
@classmethod
def define_schema(cls):
schema = EmptyImage1.define_schema()
schema.node_id = "EmptyImage2"
schema.outputs[0].is_output_list = True
return schema

@classmethod
def execute(cls, width, height, batch_size, color) -> io.NodeOutput:
return io.NodeOutput([EmptyImage1.execute(width, height, batch_size, color)[0]])
```

## Related nodes

(This nodes doesn't change outputs schema, so it runs normally at current time)

https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_extras/nodes_lt_audio.py#L37
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_api_nodes/nodes_bfl.py#L277
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_api_nodes/nodes_bfl.py#L818

Contributor guide

Open the contributing guide

Research direction

Start with comfy_api/latest/_io.py lines 2216-2263 and execution.py lines 322-327, then run the supplied nested ComfyNode reproduction with both registration orders. Done means inherited classes receive the correct independent schema regardless of initialization order, including the OUTPUT_IS_LIST setting, without breaking the related nodes mentioned.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.