/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.
$ npx -y skills add jtydhr88/comfyui-custom-node-skills --skill comfyui-node-lifecycle --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/comfyui-node-lifecycle
Context preview
The summary Claude sees to decide when to auto-load this skill.
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.
SKILL.md
comfyui-node-lifecycle.SKILL.mdname: 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
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-executenot_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.
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, width, height):
import torch
return io.NodeOutput(torch.zeros(1, height, width, 3))**V1 equivalent**:
@classmethod
def VALIDATE_INPUTS(s, width, height):
if width % 8 != 0:
return "Width must be a multiple of 8"
return TrueSkippin
Read more
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 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-executenot_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.
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, width, height):
import torch
return io.NodeOutput(torch.zeros(1, height, width, 3))**V1 equivalent**:
@classmethod
def VALIDATE_INPUTS(s, width, height):
if width % 8 != 0:
return "Width must be a multiple of 8"
return TrueSkippin
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.
Repo: jtydhr88/comfyui-custom-node-skills
Other skills on comfyui-custom-node-skills.
- /comfyui-node-advanced
ComfyUI advanced node patterns - MatchType, Autogrow, DynamicCombo, node expansion, MultiType, wildcard inputs. Use when building complex nodes with dynamic inputs, type matching, or node expansion.
Open skill - /comfyui-node-basics
ComfyUI custom node fundamentals - V3 node structure, Schema, inputs/outputs, registration. Use when creating new ComfyUI custom nodes, defining node classes, or setting up a custom node project.
Open skill - /comfyui-node-datatypes
ComfyUI data types - IMAGE, LATENT, MASK, CONDITIONING, MODEL, CLIP, VAE, AUDIO, VIDEO, 3D types, widget types, and custom types. Use when working with ComfyUI tensors, model types, or defining input/output data types.
Open skill - /comfyui-node-frontend
ComfyUI frontend JavaScript extensions - hooks, widgets, sidebar tabs, commands, settings, toasts, dialogs. Use when adding UI features to custom nodes, creating custom widgets, or extending the ComfyUI frontend.
Open skill - /comfyui-node-inputs
ComfyUI node input types - INT, FLOAT, STRING, BOOLEAN, COMBO widgets, hidden inputs, optional inputs, lazy inputs, force_input. Use when configuring node inputs, adding widgets, or customizing input behavior.
Open skill - /comfyui-node-migration
ComfyUI V1 to V3 node migration - converting legacy nodes to the V3 API. Use when migrating existing custom nodes from V1 to V3, understanding differences between API versions, or modernizing node code.
Open skill

