V3 node subclasses inherit the parent's cached RETURN_TYPES, so they cannot change their outputs
- 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
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