Comfy-Org/ComfyUI
github.com/Comfy-Org/ComfyUI ↗ · 1166 files · 19.9 MB
ComfyUI The most powerful and modular AI engine for content creation.
Overview
ComfyUI (version 0.37.0) is a node-based inference engine and aiohttp web server for diffusion and generative AI models. A single Python process runs an HTTP/WebSocket frontend server (server.PromptServer) plus a daemon 'prompt_worker' thread that pops prompt graphs off a priority queue and executes them node-by-node through execution.PromptExecutor with aggressive inter-node output caching. Its distinctive architecture is a dynamic graph executor supporting lazy inputs, list-mapping, async node functions, and runtime subgraph expansion, paired with a checkpoint auto-detection system (comfy.model_detection) that identifies model families from state-dict keys and shapes.
System map
ComfyUI is a single-process Python application: main.py parses CLI args, sets device env vars, runs custom-node prestartup scripts, loads all node definitions, initializes an optional SQLite database, then starts an aiohttp server and a daemon worker thread. The aiohttp layer (server.PromptServer) owns HTTP routes, WebSocket clients, and middleware; execution.PromptExecutor owns graph execution in the worker thread; the comfy/ package owns model loading, memory management, and sampling; nodes.py plus comfy_extras/, comfy_api_nodes/, and custom_nodes/ supply the node registry. Persistence is three-layered: an in-memory PromptQueue/history, filesystem dirs (input/output/temp/user/models) managed by folder_paths, and an Alembic-migrated SQLite DB (user/comfyui.db) for the assets system.
- Entry point is main.py: it enables args parsing via comfy.options, reads comfy.cli_args.args, sets HF_HUB_DISABLE_TELEMETRY/DO_NOT_TRACK/CUDA_VISIBLE_DEVICES env vars, runs custom-node prestartup scripts, then calls start_comfyui() which builds PromptServer, loads nodes, and spawns the prompt_worker daemon thread.
- server.PromptServer builds an aiohttp web.Application with a middleware chain (cache_control, deprecation_warning, optional compress_body, CORS or origin-only, CSP when api nodes disabled, optional comfyui_manager middleware) and registers all routes inside __init__ via a RouteTableDef.
- The worker loop prompt_worker() blocks on PromptQueue.get(timeout), calls PromptExecutor.execute(), marks the task done into history, then handles unload_models/free_memory queue flags and runs gc.collect() + model_management.soft_empty_cache() every ~10 seconds.
- Every route defined on the PromptServer RouteTableDef is duplicated under an /api prefix in add_routes() (e.g. /prompt and /api/prompt both work), plus static mounts for /extensions/<node>, /templates, /docs and the frontend web_root.
- The node registry lives in module-level dicts NODE_CLASS_MAPPINGS and NODE_DISPLAY_NAME_MAPPINGS in nodes.py; init_extra_nodes() loads ~130 built-in comfy_extras files, comfy_api_nodes glob-matched files, and external custom_nodes via load_custom_node().
- Model/MLOps core lives under comfy/: model_detection.py fingerprints checkpoints from state-dict keys, model_management.py owns device/VRAM/pinning policy, model_patcher.py owns the ModelPatcher weight-patching abstraction, samplers.py/sample.py own the sampling loop, and ldm/ holds model implementations.
- Persistent state: a SQLite database (default user/comfyui.db, override via --database-url) is created by app/database/db.py init_db() using SQLAlchemy + Alembic migrations in alembic_db/, guarded by a filelock <db>.lock against concurrent ComfyUI processes.
- The web frontend is not in this repo: it ships as the pinned pip package comfyui-frontend-package==1.53.6 and FrontendManager.init_frontend() can download a release zip from api.github.com when --front-end-version requests a specific version; --front-end-root overrides with a local path.
- CLI configuration is a module-level argparse parser in comfy/cli_args.py (~70 flags: --listen/--port, --cuda-device, VRAM modes, cache modes, dtype forcing, --disable-all-custom-nodes, --enable-manager, --database-url, --enable-assets) whose parsed `args` singleton is imported by nearly every module.
- Directory layout is configured at import time in folder_paths.py: folder_names_and_paths maps logical model categories (checkpoints, loras, vae, diffusion_models, controlnet, upscale_models, ~20 total) to search dirs, plus output/temp/input/user dirs all relocatable via --base-directory or per-dir flags.
- Runtime concurrency model: one asyncio event loop runs the HTTP server and a publish_loop() that drains a message queue fed thread-safely by send_sync() from the worker thread; WebSocket clients live in self.sockets keyed by clientId.
- Optional sub-apps and managers attach in add_routes(): UserManager (/users, /userdata), ModelFileManager (/experiment/models), CustomNodeManager (/workflow_templates, /i18n), SubgraphManager, NodeReplaceManager, and an /internal sub-application from api_server InternalRoutes.
Feature catalog
The user-facing surface is an HTTP+WebSocket API driving a node-graph execution engine, plus a large built-in node library. HTTP covers prompt submission, queue/history management, job status, image upload/view, model listing, object_info schema introspection, user data storage, and workflow templates. The node catalog spans core SD nodes in nodes.py, ~130 comfy_extras modules (audio, video, 3D, model merging, advanced samplers), and comfy_api_nodes that call paid third-party APIs. The optional ComfyUI Manager integration and the assets system (blake3-hashed media catalog in SQLite) are feature-flagged subsystems.
- POST /prompt accepts a workflow graph {node_id: {class_type, inputs}} plus optional client_id/extra_data/prompt_id/partial_execution_targets, runs validate_prompt, and heapq-pushes (number, prompt_id, prompt, extra_data, outputs_to_execute, sensitive) onto PromptQueue returning {prompt_id, number, node_errors}.
- Queue and history endpoints: GET /queue returns running+pending with sensitive fields stripped by _remove_sensitive_from_queue; POST /queue supports clear/delete; GET /history and /history/{prompt_id} read the in-memory history dict; POST /history clears or deletes entries.
- A jobs API exposes GET /api/jobs, GET /api/jobs/{job_id}, POST /api/jobs/{job_id}/cancel and POST /api/jobs/cancel backed by comfy_execution/jobs.py with JobStatus pending/in_progress/completed/failed/cancelled.
- POST /interrupt either targets a specific running prompt_id or globally interrupts via nodes.interrupt_processing -> comfy.model_management.interrupt_current_processing; POST /free sets unload_models/free_memory flags consumed by the worker loop.
- File endpoints: POST /upload/image and /upload/mask write into input/temp/output dirs with subfolder confinement checks and duplicate detection via hashing; GET /view serves files (including blake3: hash resolution to asset paths) and GET /view_metadata/{folder} exposes safetensors metadata.
- Introspection endpoints: GET /object_info and /object_info/{node_class} serialize each node class's INPUT_TYPES/RETURN_TYPES/CATEGORY for the frontend; GET /models and /models/{folder} list folder_paths categories; GET /embeddings lists embedding names; GET /system_stats and /features report device and capability info.
- The /ws WebSocket negotiates per-client feature_flags as the first message, then streams JSON events (status, executing, executed, progress, execution_start/cached/success/error) and binary frames for preview images (PREVIEW_IMAGE, UNENCODED_PREVIEW_IMAGE, PREVIEW_IMAGE_WITH_METADATA from protocol.BinaryEventTypes).
- Core node set in nodes.py covers the SD1.x/SDXL workflow: CheckpointLoaderSimple, CLIPTextEncode, KSampler/KSamplerAdvanced, VAEEncode/VAEDecode(+Tiled), LoraLoader, ControlNetLoader/Apply, EmptyLatentImage, SaveImage/PreviewImage, LoadImage, and conditioning/latent/image transform nodes.
- comfy_extras/ contributes ~130 node modules loaded by init_builtin_extra_nodes: upscale models, mask/compositing, model merging, SD3/Flux/Hunyuan/Wan/Qwen video+audio model support, 3D (load/save/mesh/gaussian splat), GLSL shaders, logic/loop/math utility nodes, and training nodes (nodes_train, nodes_dataset).
- comfy_api_nodes/ contains API nodes that call remote paid services (OpenAI, Anthropic, Gemini, Grok, Kling, Luma, ByteDance, ElevenLabs, Stability, etc.) authenticated through hidden AUTH_TOKEN_COMFY_ORG/API_KEY_COMFY_ORG extra_data keys; --disable-api-nodes blocks them and adds a restrictive CSP middleware.
- User/file management features: UserManager adds /users CRUD and /userdata file endpoints (GET/POST/DELETE/move) gated by --multi-user; get_public_user_directory blocks '__'-prefixed system user dirs from HTTP access.
- ModelFileManager adds experimental /experiment/models routes including a preview endpoint that extracts embedded preview images from model files; CustomNodeManager serves /workflow_templates and /i18n translation JSON built from loaded custom nodes.
Data flow
A prompt enters as JSON at POST /prompt, is validated into an executable output list, and becomes a heapq item on PromptQueue. The worker thread wraps it in DynamicPrompt (original + ephemeral + override layers) and builds an ExecutionList — a topological dissolve where nodes become ready when their link dependencies resolve. Each node call resolves inputs from cached upstream outputs, invokes the node's FUNCTION (mapped over input lists, async-aware), and stores outputs in signature-keyed caches; UI outputs and progress flow back over WebSocket via send_sync. Model weights flow through folder_paths resolvers into comfy.sd loaders that detect the architecture and wrap it in a ModelPatcher managed by model_management for VRAM offload. Results persist to the output dir (SaveImage) and the run record lands in the in-memory history dict (capped at 10000) plus the assets DB when enabled.
- Input boundary: POST /prompt -> trigger_on_prompt(on_prompt_handlers) -> number assignment -> node_replace_manager.apply_replacements -> execution.validate_prompt -> PromptQueue.put((number, prompt_id, prompt, extra_data, outputs_to_execute, sensitive)).
- validate_prompt collects OUTPUT_NODE classes (optionally filtered by partial_execution_targets), recursively validates inputs (type matching via validate_node_input, min/max, combo membership, custom VALIDATE_INPUTS/validate_inputs hooks), detects dependency cycles, and validates loop boundaries via validate_loops.
- Execution loop: PromptExecutor.execute_async wraps everything in torch.inference_mode, builds DynamicPrompt + ExecutionList, seeds it with output node ids, then repeatedly calls stage_node_execution -> module-level execute() -> complete/unstage until the list empties.
- Per-node execution order inside execute(): check output cache -> get_input_data resolves each input link through execution_list.get_cache (missing -> mark_missing and lazy evaluation via check_lazy_status returns PENDING) -> instantiate/cache the node object in caches.objects -> get_output_data -> _async_map_node_over_list -> node FUNCTION.
- Intermediate results are cached by CacheSet: outputs cache uses CacheKeySetInputSignature (class_type + IS_CHANGED value + ancestor input signatures, folded via to_hashable) while objects cache uses CacheKeySetID (node_id, class_type); modes are CLASSIC/LRU/NONE/RAM_PRESSURE chosen by --cache-* flags.
- Node functions can return plain tuples, dicts with 'result'/'ui'/'expand', or V3 NodeOutput objects; 'expand' produces an ephemeral subgraph injected into DynamicPrompt.ephemeral_prompt and re-scheduled, with the node's final outputs resolved later from pending_subgraph_results.
- Streaming output: nodes emit UI data via 'ui' dicts -> register_executed_outputs enriches them into asset references -> send_sync('executed', ...) to the client; sampler progress goes through comfy.utils.ProgressBar global hook -> send_sync('progress') + binary preview frames.
- Model loading path: folder_paths.get_full_path_or_raise resolves a filename inside a category's search dirs -> comfy.utils.load_torch_file reads safetensors/ckpt -> model_detection.model_config_from_unet fingerprints the state dict -> comfy.supported_models builds the model -> ModelPatcher wraps it -> load_models_gpu places it per VRAM policy.
- Sampling path inside a node: KSampler.sample -> common_ksampler -> comfy.sample.prepare_noise (seeded) -> latent_preview.prepare_callback -> comfy.sample.sample -> comfy.samplers.KSampler.sample drives the denoise loop calling model.apply_model per sigma step.
- History persistence: task_done() pops currently_running, strips the sensitive tuple element via process_item=remove_sensitive, and stores {prompt, outputs, status} in self.history keyed by prompt_id, evicting FIFO beyond MAXIMUM_HISTORY_SIZE=10000.
- Auth-sensitive extra_data keys (auth_token_comfy_org, api_key_comfy_org) are popped into a separate `sensitive` dict at enqueue time, reattached only inside the worker, and injected into node inputs via hidden input plumbing (Hidden.auth_token_comfy_org / hidden AUTH_TOKEN_COMFY_ORG).
- File output boundary: SaveImage writes PNGs (with embedded prompt/workflow metadata unless --disable-metadata) into folder_paths.get_output_directory() with counter-suffixed filenames; LoadImage reads from the input directory through annotated_filepath/get_annotated_filepath resolvers.
Behavior traces
Traced flows: (1) startup from `python main.py` through arg parsing, env setup, prestartup scripts, node loading, DB init, route registration and server bind; (2) a prompt's journey from POST /prompt through validation, queueing, per-node execution, caching and WebSocket events; (3) model loading from a CheckpointLoaderSimple node through state-dict detection into a VRAM-managed ModelPatcher; (4) interrupt and memory-pressure handling; (5) image upload and /view retrieval with traversal guards.
- Startup order: comfy.options.enable_args_parsing -> args parse -> setup_logger -> (optional) comfy_aimdo.control.init for DynamicVRAM -> apply_custom_paths (extra_model_paths.yaml, dir overrides) -> init_mime_types -> execute_prestartup_script over custom_nodes -> import execution/server/nodes -> start_comfyui: PromptServer(loop) -> init_extra_nodes -> set_cudnn_benchmark -> setup_database(init_db + asset_manager.startup) -> add_routes -> hijack_progress -> prompt_worker thread -> prompt_server.setup + run -> TCPSite bind on --listen/--port.
- Prompt submission order: post_prompt -> request.json -> trigger_on_prompt -> client prompt_id validation via validate_job_id (canonical UUID) -> node_replace_manager.apply_replacements -> validate_prompt -> sensitive key extraction -> prompt_queue.put -> queue_updated() broadcasts status -> worker q.get pops the item.
- Worker execution order: prompt_worker gets item -> asset_manager.pause_background_scan -> PromptExecutor.execute -> execute_async: set_preview_method -> interrupt_processing(False) -> execution_start msg -> torch.inference_mode -> DynamicPrompt + reset_progress_state -> caches.set_prompt/clean_unused -> execution_cached msg -> loop stage_node_execution/execute -> execution_success or handle_execution_error -> task_done stores history -> send 'executing' node=None.
- Single node step order inside execute(): dynprompt id mapping (real/display/parent) -> caches.outputs.get short-circuit -> get_input_data (links via execution_list.get_cache, hidden PROMPT/DYNPROMPT/EXTRA_PNGINFO/UNIQUE_ID injection) -> send 'executing' -> lazy check via check_lazy_status -> get_output_data -> _async_map_node_over_list (slices list inputs, asyncio.create_task for coroutine functions, ExecutionBlocker propagation) -> merge_result_data -> caches.outputs.set + execution_list.cache_update -> 'executed' event.
- Interrupt trace: POST /interrupt -> nodes.interrupt_processing -> comfy.model_management.interrupt_current_processing(True) -> flag checked by throw_exception_if_processing_interrupted inside the progress hook and before_node_execution -> InterruptProcessingException caught in execute() -> handle_execution_error sends 'execution_interrupted'.
- Checkpoint load trace: CheckpointLoaderSimple node -> comfy.sd.load_checkpoint_guess_config -> utils.load_torch_file -> model_detection.unet_prefix_from_state_dict + model_config_from_unet (state-dict key/shape fingerprinting) -> model_config classes from comfy.supported_models -> CLIP/VAE patchers built -> ModelPatcher returned as MODEL output -> later load_models_gpu/free_memory decide device placement and weight offloading.
- Image upload trace: POST /upload/image -> request.post() -> image_upload -> get_dir_by_type(input/temp/output) -> normpath join + commonpath containment check -> hash-compare dedup loop renaming 'name (i).ext' -> write file -> asset_manager.register_upload -> JSON {name, subfolder, type, asset?}.
- WebSocket lifecycle: GET /ws -> ws.prepare -> sid from clientId query or uuid4 -> sockets[sid]=ws -> send 'status' with queue info -> first TEXT message may carry feature_flags (stored in sockets_metadata, server flags replied) -> all server->client traffic funnels through send_sync -> messages queue -> publish_loop -> send_json/send_bytes.
- OOM/error path: exception inside execute() -> formatted inputs captured -> comfy.model_management.is_oom check triggers unload_all_models and debug_memory_summary logging -> error_details {node_id, exception_message, exception_type, traceback, current_inputs} -> handle_execution_error -> 'execution_error' WebSocket message -> worker records status 'error' in history.
- RAM-pressure caching behavior: with default RAM_PRESSURE cache, execute_async wires ram_release callback into memory_management.set_ram_cache_release_state; after each node it releases toward inactive headroom, computes shortfall vs virtual_memory_available, frees large intermediates then pins if needed.
Testing signals
Testing is pytest-based and split into two trees: tests-unit (fast unit tests, run in CI on Linux/Windows/macOS with CPU torch) and tests (integration: execution tests that spawn a real server subprocess and drive it over WebSocket, and inference tests that run full workflows and compare generated images against a baseline directory). Markers 'inference' and 'execution' allow deselection. Coverage of the node library itself is thin — most tests target the execution engine, validation, caching, and server endpoints rather than model math.
- pytest.ini sets testpaths to both tests and tests-unit, registers 'inference' and 'execution' markers, and adds -s (no output capture); pythonpath=. makes the repo root importable.
- tests/execution/test_execution.py spins up the real server via subprocess.Popen([sys.executable, 'main.py', ...]), waits, connects a ComfyClient over websocket-client, submits prompt graphs built with GraphBuilder, and asserts on WS events and output images.
- tests/execution also covers async nodes, jobs API, preview method override, progress isolation, and the public API (test_public_api.py), with a testing_nodes helper package of custom nodes for the tests.
- tests/inference/test_inference.py runs real diffusion workflows (ComfyGraph wrapper, KSampler imported from comfy.samplers) writing images to --output_dir; tests/compare performs quality regression by comparing directories, and conftest.py deliberately runs test_quality last.
- tests-unit covers the execution engine (test_execution_list, validate_node_input, loop validation, nested loops, cache providers, reentry), server/user endpoints (prompt_server_test, system_user_endpoint_test), folder_paths, security (security_test dir), assets, and comfy internals — all CPU-only.
- CI: .github/workflows/test-unit.yml runs `pytest tests-unit` on ubuntu-latest/windows-2022/macos-latest with Python 3.12 and CPU-only torch; it is marked continue-on-error, so unit failures do not block merges.
- Additional workflows: test-execution.yml and test-ci.yml run the execution/integration suites, test-launch.yml smoke-tests server launch (--quick-test-for-ci exits after startup), and ruff.yml lints with the rule set pinned in pyproject.toml.
- Test deps are separate: tests-unit/requirements.txt plus websocket-client/opencv/scikit-image for integration tests per tests/README.md; inference tests require downloaded models, so they are not runnable in plain CI.
- No coverage tooling, no mocked-HTTP layer for aiohttp routes, and no tests under comfy/ for individual model implementations — model correctness is only exercised indirectly through the heavyweight inference suite. (inferred)
- A --quick-test-for-ci flag in main.py exits right after the worker thread starts, which is what test-launch.yml uses to verify the server boots cleanly.
Risk map
Biggest risks: there is no authentication on the HTTP API, custom nodes are arbitrary code executed at startup, and model file formats include pickle-capable extensions. The codebase has accumulated scattered path-traversal and XSS mitigations that must be replicated at every new file endpoint. The execution engine is a single shared mutable state machine (server.client_id, last_node_id, module-level registries) so concurrent or reentrant use is fragile. Memory management and the aimdo/DynamicVRAM integration contain version-dependent protocol fallbacks that are easy to break.
- No authentication exists on any route: default bind is 127.0.0.1, but --listen with no value exposes 0.0.0.0,:: — an unauthenticated API that can queue prompts, read/write userdata files, upload images, and hit /internal/logs.
- Custom nodes are arbitrary Python: execute_prestartup_script exec_modules every custom_nodes/*/prestartup_script.py before the server starts, and load_custom_node exec_modules each module — remote-code-execution by design; mitigations are only --disable-all-custom-nodes/--whitelist-custom-nodes and optional comfyui_manager policy.
- supported_pt_extensions includes .ckpt/.pt/.bin/.pkl — formats that can embed pickle payloads; loading relies on comfy.utils.load_torch_file's safe handling, so changes there are security-critical. (inferred)
- Path traversal defenses are reimplemented per endpoint: /view checks '..' and leading '/', subfolder joins use os.path.commonpath containment, and folder_paths.is_within_directory/get_annotated_filepath add realpath-based containment — any new file-serving endpoint must repeat this correctly.
- Stored-XSS mitigation is centralized in folder_paths.DANGEROUS_CONTENT_TYPES/is_dangerous_content_type with a Sec-Fetch-Dest:image carve-out for SVG; endpoints serving user content must route dangerous types to octet-stream or they reintroduce XSS.
- origin_only_middleware is an admitted partial fix for DNS-rebinding/cross-site POSTs to a loopback server ('the proper fix would be to add a cookie'): it 403s Sec-Fetch-Site:cross-site and Host/Origin hostname mismatches only when Host resolves to loopback.
- Shared mutable globals: server.client_id and last_node_id are single-slot fields on PromptServer set from extra_data of whichever prompt is running, and NODE_CLASS_MAPPINGS is a module-level dict mutated by every extension — one concurrently executing prompt or a misbehaving custom node can corrupt routing/display state. (inferred)
- comfy_aimdo protocol drift is handled by chained TypeError fallbacks (0.4.9/0.4.10 signatures) in main.py; silent wrong-version behavior is a real hazard since the fallbacks catch TypeError broadly.
- The queue item is a positional tuple where index 5 is `sensitive` extra data; _remove_sensitive_from_queue and the remove_sensitive lambda both rely on tuple position, so reordering the tuple silently leaks auth tokens into queue/history responses.
- hook_breaker_ac10a0.py is a manual save/restore shim that protects comfy.model_management.cast_to from being hooked by custom nodes — evidence that custom-node monkeypatching has caused production incidents and protection is opt-in per function.
- DB init kills the process on lock contention only when --enable-assets is set; otherwise it logs and continues with a degraded assets layer — divergence between code paths makes failures mode-dependent.
- Alembic migration 0007_record_content_split is explicitly destructive: upgrading through it discards the prior asset catalog (tags, metadata, previews, renames) keeping only a .bkp file.
Extension points
ComfyUI's primary extension mechanism is the custom_nodes directory scanned at startup: V1 extensions export NODE_CLASS_MAPPINGS/NODE_DISPLAY_NAME_MAPPINGS dicts (and optional WEB_DIRECTORY for frontend JS), while V3 extensions expose a comfy_entrypoint() returning a ComfyExtension whose get_node_list() yields ComfyNode classes with declarative Schemas. Secondary hooks: prestartup_script.py per package, server.on_prompt_handlers for prompt mutation, custom aiohttp routes via PromptServer.instance.routes, extra model paths via yaml config, progress handlers, cache providers, and node replacement rules.
- V1 node contract: a module in custom_nodes/ exporting NODE_CLASS_MAPPINGS {name: class} where each class defines INPUT_TYPES (required/optional/hidden), RETURN_TYPES, FUNCTION (method name), CATEGORY; OUTPUT_NODE=True makes it an execution target; registered into the global registry by load_custom_node.
- V3 node contract: modules export comfy_entrypoint() (sync or async) returning a ComfyExtension; get_node_list() returns io.ComfyNode subclasses whose GET_SCHEMA() provides node_id, inputs/outputs as typed Input/Output objects, hidden flags, and behavior flags (is_output_node, not_idempotent, enable_expand, accept_all_inputs, loop_boundary).
- Frontend extension point: WEB_DIRECTORY module attr or pyproject.toml [tool.comfy] web dir registers a static JS dir served at /extensions/<name>; /extensions endpoint enumerates all JS for the frontend to import.
- Per-package prestartup hook: any custom_nodes/<pkg>/prestartup_script.py is exec'd before node loading, letting packages patch the environment early (used e.g. for dependency installs).
- Prompt mutation hook: PromptServer.add_on_prompt_handler registers callables run by trigger_on_prompt on every POST /prompt body before validation (exceptions are logged and skipped).
- HTTP route extension: custom nodes can append to PromptServer.instance.routes (the same RouteTableDef), and every added route is automatically mirrored under /api by add_routes. (inferred)
- Model path extension: extra_model_paths.yaml (example in repo root) and --extra-model-paths-config call utils.extra_config.load_extra_path_config which feeds folder_paths.add_model_folder_path to add search dirs per category.
- Execution hooks for nodes: IS_CHANGED/fingerprint_inputs controls cache reuse; check_lazy_status marks required inputs for lazy evaluation; VALIDATE_INPUTS/validate_inputs custom-validates; ExecutionBlocker short-circuits branches; GraphBuilder lets a node emit an 'expand' subgraph.
- Cache providers: comfy_execution/cache_provider.py exposes _get_cache_providers()/_has_cache_providers and PromptExecutor calls provider.on_prompt_start/on_prompt_end around each run — an external cache persistence seam.
- Progress reporting is extensible via add_progress_handler in comfy_execution/progress.py; WebUIProgressHandler is the built-in implementation forwarding to the websocket client.
- Feature flags: comfy_api/feature_flags.py maintains CLI_FEATURE_FLAG_REGISTRY (--list-feature-flag dumps it) and per-connection negotiation lets clients declare capabilities like supports_preview_metadata that change server behavior.
- Comfy API versioning seam: comfy_api/version_list.supported_versions are registered through register_versions(ComfyAPIWithVersion) in init_public_apis, letting custom nodes target a stable versioned API surface (v0_0_1, v0_0_2, latest).
Contributor handoff
AGENTS.md is unusually prescriptive and is the first file to read: it demands minimal diffs, no new dependencies, no outbound network calls in core, strict layer boundaries (execution.py must not learn API/persistence concepts), no torch.no_grad wrappers (inference_mode already wraps execution), and reuse of comfy.ops/quant_ops/Comfy Kitchen optimized ops for model code. Read main.py -> server.py -> execution.py -> nodes.py -> folder_paths.py -> comfy/cli_args.py to understand any user-facing change. Tests: pytest tests-unit for fast checks; tests/execution needs a spawned server; tests/inference needs real models. Watch the traps: import-time side effects everywhere, tuple-indexed queue items, three different node ids (real/display/ephemeral), and signature-based caching that silently reuses outputs.
- AGENTS.md mandates: smallest possible diff, no new dependencies, no internet requests from core ComfyUI (no telemetry/upload/update-check code paths), and aggressive deletion of dead compat code.
- AGENTS.md architecture rule: keep layer boundaries — execution.py consumes the prompt graph and must not know workflow/frontend/persistence ids; fix leaks with caller-side mapping or narrow interfaces rather than passing context objects through.
- AGENTS.md model rules: never add torch.no_grad/inference_mode wrappers (execution is already inside torch.inference_mode), never add freeze/trainability toggles, prefer existing optimized ops in comfy.ops/comfy.quant_ops/Comfy Kitchen, and DiT models must tolerate non-patch-multiple latents via pad_to_patch_size.
- Import order is load-bearing: torch must not be imported before main.py's late import block (it warns if 'torch' is in sys.modules), which is why ExecutionBlocker lives in comfy_execution/graph_utils.py rather than graph.py — keep heavy imports out of modules parsed early.
- The args singleton from comfy.cli_args is parsed at import time and read by folder_paths at import time to fix base_path/models_dir — tests and embedding code must control argv or env carefully; comfy.options.enable_args_parsing() is the gate.
- Queue tuple layout is positional and load-bearing: (number, prompt_id, prompt, extra_data, outputs_to_execute, sensitive); index 5 is stripped before exposure — preserve arity when modifying put/get/task_done or the remove_sensitive lambdas.
- Three node-id namespaces coexist: ephemeral ids (created by expand), display ids (UI mapping), and real ids (original prompt); always use dynprompt.get_real_node_id/get_display_node_id rather than raw ids in execution code.
- Caching can silently skip your code path: outputs are keyed by input signature including IS_CHANGED/fingerprint_inputs — when debugging a node that 'doesn't run', check CacheKeySetInputSignature and the node flags NOT_IDEMPOTENT / UNIQUE_ID hidden input first.
- Safe edit boundaries: add a node in nodes.py or a comfy_extras/nodes_*.py file (register in NODE_CLASS_MAPPINGS), add routes in the owning app/* manager, add model support via comfy/model_detection.py signature + comfy/supported_models entry — do not create parallel registries.
- Run `pytest tests-unit` after changes; unit CI is continue-on-error so green CI does not prove correctness — for executor changes also run tests/execution which needs websocket-client and spawns main.py as a subprocess.
- When touching file-serving endpoints, reuse folder_paths.is_within_directory/get_annotated_filepath and is_dangerous_content_type rather than hand-rolling checks — traversal/XSS guards are the most-reviewed security surface.
- Style: ruff lint (E, W, F, T print-usage, S102/S307, N805) with E501/E722/E731/E402 ignored; comments sparse; match local style — long lines and module-level state are normal here.
Languages
| Language | Files | Share of source |
|---|---|---|
| Python | 916 | 100% |
| JSON | 155 | docs/config |
| YAML | 35 | docs/config |
| Markdown | 13 | docs/config |
| Shell | 1 | 0% |
| TOML | 1 | 0% |
Start reading here
Probable source entrypoint: app/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
app/ (43 files)
Repository structure
| Top-level path | Files |
|---|---|
comfy/ | 362 |
tests-unit/ | 190 |
comfy_extras/ | 165 |
blueprints/ | 133 |
comfy_api_nodes/ | 94 |
app/ | 43 |
comfy_api/ | 39 |
.github/ | 34 |
Docs found: AGENTS.mdCONTRIBUTING.mdREADME.md
Package
pyproject.toml — ComfyUI v0.37.0, 0 declared dependencies.
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructionsmain.py— Python entrypoint.github/workflows/windows_release_dependencies_manual.yml— GitHub Actions workflow.github/workflows/windows_release_package.yml— GitHub Actions workflow.github/workflows/test-ci.yml— GitHub Actions workflow.github/workflows/test-launch.yml— GitHub Actions workflow.github/workflows/check-ai-co-authors.yml— GitHub Actions workflow.github/workflows/windows_release_dependencies.yml— GitHub Actions workflow.github/workflows/test-build.yml— GitHub Actions workflow
Structural health signals
Average score 5.6/10 across 96 analyzed source files; 95 hotspots surfaced.
comfy_api_nodes/nodes_bytedance.py— 1191 lines, score 4.7 (Large file, Deep nesting)comfy_api_nodes/nodes_wan.py— 1252 lines, score 4.7 (Large file, Deep nesting)comfy_api_nodes/nodes_kling.py— 1208 lines, score 4.7 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
b33e2b55cae02026-09-22 — chore: update workflow templates to v0.11.68 (#16466)b09760de30792026-09-22 — [Partner Nodes] feat(Tencent): add Hunyuan Image 3.5 text-to-image and edit nodes (#16462)e638023d54492026-09-21 — Fast disk detection to all model loaders. (#16425)fc584aaa22652026-09-22 — Blend H3 VAE tiles against composited neighbours (#16436)d158420950922026-09-21 — Cuda graphs + memory compiler on ace step 1.5 autoregressive model. (#16461)