Prompt validation fails on deep acyclic workflows
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 158
Description
## Custom Node Testing
- [x] Reproduced without any installed custom-node packages. The script below registers two in-memory test node classes only to build a deterministic acyclic graph.
## Expected Behavior
A valid acyclic prompt graph should pass validation without depending on Python's recursion limit.
## Actual Behavior
`validate_inputs()` recursively awaits itself once per linked node. On current `master` at `917faef771a2fd2f14f44af94f17da3d0b2803a3`, the exact cutoff moves slightly with the caller's stack depth, but it is close to 1,000 linked nodes. A 2,000-node linear graph reliably returns an invalid result even though it is acyclic:
```text
result: (False, [], '1999')
validated node: 1020
type: exception_during_inner_validation
message: Exception when validating inner node
details: maximum recursion depth exceeded
exception_type: RecursionError
```
## Steps to Reproduce
Run this from the ComfyUI repository with its normal Python environment:
```python
import asyncio
import execution
class Source:
RETURN_TYPES = ("INT",)
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
class Link:
RETURN_TYPES = ("INT",)
@classmethod
def INPUT_TYPES(cls):
return {"required": {"value": ("INT", {})}}
execution.nodes.NODE_CLASS_MAPPINGS["ValidationSource"] = Source
execution.nodes.NODE_CLASS_MAPPINGS["ValidationLink"] = Link
size = 2_000
prompt = {"0": {"class_type": "ValidationSource", "inputs": {}}}
for index in range(1, size):
prompt[str(index)] = {
"class_type": "ValidationLink",
"inputs": {"value": [str(index - 1), 0]},
}
validated = {}
result = asyncio.run(
execution.validate_inputs("repro", prompt, str(size - 1), validated)
)
print(result)
print(next(value for value in validated.values() if value[1]))
```
## Debug Logs
```text
RecursionError: maximum recursion depth exceeded
```
The exception is caught by the parent validation frame and stored as `exception_during_inner_validation`, so the root call returns `False` rather than raising.
## Other
I tested an explicit-stack prototype before proposing a PR. It validates 1,000, 2,000, and 10,000-node chains and matched the recursive implementation for awaited validator rewrites, cancellation and exceptions, shared-DAG caching, partial outputs, custom-validator ordering, and 4,096 overlapping-cycle graphs.
There is a real tradeoff to discuss first:
- On a 4,000-node linear chain, with the reference recursion limit raised only for comparison, two paired CPU-time runs measured `72.55 ms -> 10.68 ms` and `73.73 ms -> 10.39 ms` (about 6.8-7.1x).
- On ordinary fan-in and shared-DAG fixtures, the prototype was about 44-46% slower, although the absolute increase was only 0.16-0.22 ms per validation.
- The implementation is materially larger than the current recursive function and would need broader upstream regression coverage before a PR.
A prior [public autoresearch trajectory](https://dashboard.weco.ai/share/mpQ420TdrbtI68sx-BE6MZoiUL-VlEdU) records the explicit-stack direction. I am not treating that prototype as PR-ready. Would maintainers accept an iterative implementation with the measured sub-millisecond ordinary-graph overhead, or should this remain recursive unless a simpler approach avoids that regression?
The source-pinned evaluator, repeated measurements, and current NO-GO report are available in [`dexhunter/osspr-automation`](https://github.com/dexhunter/osspr-automation/tree/e84b0a800c78ff52a8a414066b9718c28d2e8208/remote_metric_scripts/Comfy-Org__ComfyUI__deep_prompt_validation). The tightened evaluator rejects the current prototype on the ordinary fan-in guard.
Contributor guide
Research direction
Start at execution.validate_inputs and run the supplied 2,000-node reproduction in ComfyUI's normal Python environment. Compare a proposed validation approach against the documented deep-chain, shared-DAG, cancellation, exception, partial-output, custom-validator, and cycle cases; done means valid deep acyclic graphs pass without recursion failure while existing behavior is preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100