Skip to content

/comfyui-node-lifecycle

ComfyUI node execution lifecycle - caching, fingerprint_inputs/IS_CHANGED, validate_inputs/VALIDATE_INPUTS, check_lazy_status, execution order. Use when debugging execution, implementing caching control, input validation, or understanding execution flow.

From plugin
comfyui-custom-node-skills
3129 skills

Claude Code

$ npx -y skills add jtydhr88/comfyui-custom-node-skills --skill comfyui-node-lifecycle --agent claude-code

SKILL.md

comfyui-node-lifecycle.SKILL.md
name: comfyui-node-lifecycle
description: ComfyUI node execution lifecycle - caching, fingerprint_inputs/IS_CHANGED, validate_inputs/VALIDATE_INPUTS, check_lazy_status, execution order. Use when debugging execution, implementing caching control, input validation, or understanding execution flow.

ComfyUI Node Execution Lifecycle

Understanding the execution lifecycle helps build efficient, correct nodes.

Execution Flow Overview

1. Prompt received from frontend
2. Validation phase
   ├── Look up each node class
   ├── Call INPUT_TYPES() / define_schema() for input specs
   ├── Validate connections and types
   ├── Call validate_inputs() for each node
   └── Validate loop boundaries (StartLoop/EndLoop pairing, v0.36+)
3. Build execution order (topological sort from output nodes)
4. For each node in order:
   ├── Cache check (fingerprint_inputs)
   ├── Input resolution (get upstream values)
   ├── Lazy evaluation (check_lazy_status)
   ├── Execute function
   └── Store outputs in cache
5. Return results to frontend

Execution Order

ComfyUI executes from **output nodes backward**: 1. Identifies output nodes (`is_output_node=True`) 2. Builds dependency graph 3. Topological sort determines execution order 4. Only nodes connected to output nodes execute

Cache Control: fingerprint_inputs (V3) / IS_CHANGED (V1)

Controls when a node re-executes vs uses cached results.

class RandomNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="RandomNode",
            display_name="Random Value",
            category="utils",
            inputs=[
                io.Float.Input("min_val", default=0.0),
                io.Float.Input("max_val", default=1.0),
            ],
            outputs=[io.Float.Output("FLOAT")],
        )

    @classmethod
    def fingerprint_inputs(cls, min_val, max_val):
        """Return value compared to last run. Different value = re-execute."""
        # Return unique value each time to always re-execute
        import time
        return time.time()

    @classmethod
    def execute(cls, min_val, max_val):
        import random
        return io.NodeOutput(random.uniform(min_val, max_val))

**How caching works**:

  • Before execution, `fingerprint_inputs()` is called with the same args as `execute()`
  • Return value is compared to the previous run's return value
  • If **same** → skip execution, use cached output
  • If **different** → re-execute the node
  • If `fingerprint_inputs` is not defined → cache based on input values

**V1 equivalent** (`IS_CHANGED`):

@classmethod
def IS_CHANGED(s, min_val, max_val):
    return time.time()  # always re-execute

not_idempotent Flag

For nodes that should never be cached:

io.Schema(
    node_id="AlwaysRunNode",
    not_idempotent=True,  # prevents cache sharing between instances of the same node
    # ...
)

> **Important:** `not_idempotent=True` does **not** prevent a node from reusing its own cached output on subsequent runs. It only prevents cache sharing between different instances of the same node type that have identical inputs. To force re-execution every run (e.g., for file-writing nodes), you must also implement `fingerprint_inputs` (V3) or `IS_CHANGED` (V1) returning a unique value each time.

has_intermediate_output Flag

For nodes with interactive UI that produce intermediate outputs (e.g., Image Crop, Painter). These behave like output nodes (UI results are cached and resent to the frontend on page refresh) but do NOT automatically get added to the execution list — they only execute if on the dependency path of a real output node.

io.Schema(
    node_id="InteractiveCropNode",
    has_intermediate_output=True,
    # ...
)

External Cache Providers

Share cached node outputs across ComfyUI instances (e.g. a shared network cache) by registering a `CacheProvider`:

from comfy_api.latest import Caching

class MyCacheProvider(Caching.CacheProvider):
    async def on_lookup(self, context):   # context: node_id, class_type, cache_key_hash
        ...  # return Caching.CacheValue(outputs=[...], ui={...}) or None on miss

    async def on_store(self, context, value):
        ...  # store to external storage (dispatched as a background task)

    def should_cache(self, context, value=None) -> bool:
        return True  # return False to skip external caching for a node

    def on_prompt_start(self, prompt_id): ...
    def on_prompt_end(self, prompt_id): ...

# Register in ComfyExtension.on_load():
api = ComfyAPI()
await api.caching.register_provider(MyCacheProvider())

Providers are consulted on local cache miss, in registration order. Exceptions from providers never break execution.

On a cache hit the cached UI is resent (with `id`/`preview_id` stripped from UI entries — see `comfyui-node-outputs`). For RAM-cache sizing of custom output objects see `_comfy_cache_tensors` in `comfyui-node-datatypes`.

Input Validation: validate_inputs (V3) / VALIDATE_INPUTS (V1)

Validates inputs before execution. Runs during the validation phase.

class ValidatedNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="ValidatedNode",
            display_name="Validated Node",
            category="utils",
            inputs=[
                io.Int.Input("width", default=512, min=1, max=8192),
                io.Int.Input("height", default=512, min=1, max=8192),
            ],
            outputs=[io.Image.Output("IMAGE")],
        )

    @classmethod
    def validate_inputs(cls, width, height):
        """Return True if valid, or error string if invalid."""
        if width % 8 != 0 or height % 8 != 0:
            return "Width and height must be multiples of 8"
        if width * height > 4096 * 4096:
            return "Total pixels exceed maximum (4096x4096)"
        return True

    @classmethod
    def execute(cls
Read more
@jtydhr88
Ships withcomfyui-custom-node-skills

A curated collection of agent skills (for Claude Code and OpenAI Codex) for developing ComfyUI custom nodes. These skills give the agent comprehensive knowledge of the ComfyUI node system, covering both the V3 (recommended) and V1 (legacy) APIs.

Stars on GitHub

Momentum

stars in total
312
Star history of jtydhr88/comfyui-custom-node-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about comfyui-node-lifecycle.

Which coding agents can use comfyui-node-lifecycle?
comfyui-node-lifecycle installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.