Comfy-Org / Comfy-Org/ComfyUI

V3 node subclasses inherit the parent's cached RETURN_TYPES, so they cannot change their outputs

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

Description

### Summary

A V3 (`io.ComfyNode`) subclass that overrides `define_schema()` to change its outputs keeps the **parent's** `RETURN_TYPES`. The cache sentinels in `comfy_api/latest/_io.py` are plain class attributes, so a subclass inherits an already-populated `_RETURN_TYPES` and the `is None` guard never fires — while `cls.SCHEMA` is assigned unconditionally and is correct.

In effect, no V3 subclass can change its outputs.

Version `v0.34.0-56-g250b2e95`, Python 3.14.7. Still present on `master`.

### The code

`comfy_api/latest/_io.py:2267`, inside `GET_SCHEMA()`:

```python
if cls._RETURN_TYPES is None: # inherited from parent -> already populated
...
cls._RETURN_TYPES = output
cls._RETURN_NAMES = output_name
cls._OUTPUT_IS_LIST = output_is_list
cls._OUTPUT_TOOLTIPS = output_tooltips
cls.SCHEMA = schema # unconditional -> subclass schema IS correct
```

`_RETURN_TYPES = None` is declared once on the base at line 2182. Once a parent has been schema'd — which registration does — every subclass sees a non-`None` value by ordinary attribute inheritance and skips the block. The same `if cls._X is None` pattern guards `_API_NODE`, `_OUTPUT_NODE`, `_HAS_INTERMEDIATE_OUTPUT`, `_INPUT_IS_LIST`, `_NOT_IDEMPOTENT` and `_ACCEPT_ALL_INPUTS` just above.

### Reproduction

Core only, no custom nodes:

```python
from comfy_api.latest import io

class Parent(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(node_id="Parent", category="test", inputs=[],
outputs=[io.Conditioning.Output(), io.Latent.Output()])
@classmethod
def execute(cls):
return io.NodeOutput(None, None)

class Child(Parent):
@classmethod
def define_schema(cls):
schema = super().define_schema()
schema.node_id = "Child"
schema.outputs.insert(0, io.Model.Output(display_name="model"))
return schema

print(Child.RETURN_TYPES) # ['CONDITIONING', 'LATENT']
print([o.io_type for o in Child.define_schema().outputs]) # ['MODEL', 'CONDITIONING', 'LATENT']
print(Child.RETURN_TYPES is Parent.RETURN_TYPES) # True
```

Expected `Child.RETURN_TYPES == ['MODEL', 'CONDITIONING', 'LATENT']`. `RETURN_NAMES` is stale too. The parent must be schema'd first, as registration does.

### Why it surfaces badly

`/object_info` doesn't use `RETURN_TYPES` for V3 nodes — `server.py:753` routes them to schema-derived `GET_NODE_INFO_V1()` — so the node advertises correct outputs to the API and frontend and only validation disagrees. `execution.py:932` then indexes the stale tuple (`received_type = r[val[1]]`), giving either `IndexError: list index out of range` — caught by the caller, so it is reported as `Exception when validating inner node` against an innocent node downstream — or an off-by-one `Return type mismatch`. Neither names the node holding the stale attribute, and auditing the graph against `/object_info` shows nothing wrong.

### Suggested fix

Test the class's own `__dict__` rather than the inherited value:

```python
if "_RETURN_TYPES" not in cls.__dict__:
```

or set the sentinels in `__init_subclass__`. Same for the other cached attributes. Node authors can work around it today with `_RETURN_TYPES = None` in the subclass body (verified).

Contributor guide

Open the contributing guide

Research direction

Start in comfy_api/latest/_io.py at ComfyNode.GET_SCHEMA around line 2267, then inspect the related cached-attribute guards above it. Use the Parent and Child reproduction from the issue, with the parent schema'd first as registration does, and compare the subclass's return metadata with its schema. Done means subclass outputs and names are reflected in validation without breaking the other cached attributes; server.py and execution.py show where the stale metadata surfaces.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.