Replace all bare ComfyUI internal imports with centralized _load_module()
using importlib.util.spec_from_file_location(). This eliminates sys.path
shadowing attacks, removes the remove/re-insert gap window, and makes
import resolution deterministic regardless of working directory, install
layout, or prior sys.path state.
node_runtime.py — core rewrite:
- Add _load_module(): centralized importlib isolation layer that loads
modules from explicit file paths verified against a ComfyUI root
confirmed by _is_comfyui_directory() (checks for nodes.py marker)
- Add _is_comfyui_directory(): structural verification of candidate paths
- Add _find_from_extension_location(): walk up from __file__ via realpath
to find ComfyUI root (handles symlinked installs)
- Rewrite get_comfyui_path(): multi-strategy fallback chain:
(1) COMFYUI_PATH env var with verification, (2) relative extension walk,
(3) CWD walk as depth-limited last resort (max 20 levels)
- Replace bare 'from nodes import NODE_CLASS_MAPPINGS' with _load_module()
- Replace bare 'import execution/server' with _load_module()
- Replace bare 'from main import load_extra_path_config' with _load_module()
(alias "comfy_main" to prevent sys.modules cache collision)
- Replace bare 'from utils.extra_config import ...' with _load_module()
- Replace bare 'import comfy.options/cli_args' with _load_module()
- Replace bare 'import cuda_malloc' with _load_module()
- Rewrite add_comfyui_directory_to_sys_path(): idempotent insert-once,
no remove/re-insert gap window
- Rewrite import_custom_nodes(): _load_module() for execution/nodes/server,
eliminated sys.path.remove() that created a gap window for lazy imports
- Rewrite get_node_class_mappings(): _load_module() for nodes.py
- Replace all print() statements with logging.DEBUG (no path leaks to stdout)
- Add None guards on args attributes after cli_args module load
generator/render.py — generated script isolation:
- Embed _load_module(), _is_comfyui_directory(), _find_from_extension_location()
in generated scripts via inspect.getsource()
- Add importlib.util and logging imports to generated output
- Generated scripts now inherit full importlib isolation guarantee — zero
bare imports of ComfyUI internals
generator/generated_helpers.py:
- Export _load_module, _is_comfyui_directory, _find_from_extension_location
for use by render.py
tests/test_import_path_resolution.py — new (14 test cases):
- TestIsComfyuiDirectory: structural verification against real ComfyUI checkout,
rejection of empty/nonexistent directories
- TestLoadModule: module loading from explicit path, graceful None for missing
- TestShadowingResistance: malicious main.py and nodes.py at sys.path[0] are
ignored; _load_module() always loads from verified file path
- TestSysPathIdempotence: insert-once behavior, no remove/re-insert in
import_custom_nodes(), no print() statements in module source
- TestGeneratedScriptIsolation: generated output contains _load_module() and
spec_from_file_location; zero bare ComfyUI imports
- TestGetComfyuiPath: env var strategy with verification, fallback chain
tests/test_upscale_model_loader_export.py — updated assertions:
- Replace "import comfy.options" / "import cuda_malloc" checks with
_load_module() string pattern matching ("comfy.options", "cuda_malloc")
- Update ordering assertion for cuda_malloc/torch import sequence
Fixes: #105, #117 (utils package shadowing), #19 (FileNotFoundError on Windows)
Addresses security audit findings: #1 (COMFYUI_PATH poisoning), #2 (directory
collision), #3 (main.py shadowing), #6 (gap window race), #7 (unbounded walk),
#8 (path confusion), #11 (stdout path leak), #12 (symlink resolution)
Spec: docs/specs/harden-import-path-resolution.html
ComfyUI-to-Python-Extension
Build a workflow in ComfyUI, then walk away with runnable Python.
ComfyUI-to-Python-Extension turns visual workflows into executable scripts so you can move from node graphs to automation, experiments, and repeatable generation without rebuilding everything by hand.
This project supports:
- exporting from the ComfyUI UI with
Save As Script - converting saved API-format workflows with the CLI
Install
Choose the setup that matches how you want to use the project. This project supports Python 3.12 and newer.
Web UI extension (File -> Save As Script)
For ComfyUI to recognize this project as an extension, the repo must be discoverable through ComfyUI's custom_nodes search paths.
Clone directly into ComfyUI/custom_nodes:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/pydn/ComfyUI-to-Python-Extension.git
Or keep the repo elsewhere, then either:
- symlink it into
ComfyUI/custom_nodes - add its parent directory to ComfyUI's
custom_nodessearch paths viaextra_model_paths.yaml
Example symlink setup:
git clone https://github.com/pydn/ComfyUI-to-Python-Extension.git
cd /path/to/ComfyUI/custom_nodes
ln -s /path/to/ComfyUI-to-Python-Extension ComfyUI-to-Python-Extension
Then install this extension into the same Python environment that launches ComfyUI.
The pyproject.toml file declares the package dependencies, but those dependencies still need to be installed into ComfyUI's runtime Python.
If you run ComfyUI from a source checkout with uv:
cd /path/to/ComfyUI
uv pip install -e ./custom_nodes/ComfyUI-to-Python-Extension
uv run python main.py
If you use the Windows portable build:
cd C:\path\to\ComfyUI_windows_portable\ComfyUI\custom_nodes\ComfyUI-to-Python-Extension
..\..\..\python_embeded\python.exe -m pip install -e .
Running uv sync inside ComfyUI-to-Python-Extension creates this extension's own .venv.
ComfyUI does not automatically import dependencies from that .venv; it imports custom nodes with the Python interpreter used to launch ComfyUI.
After installation, restart ComfyUI.
CLI exporter / generated scripts
You can keep the repo anywhere for CLI usage and generated-script execution.
git clone https://github.com/pydn/ComfyUI-to-Python-Extension.git
cd ComfyUI-to-Python-Extension
uv sync
export COMFYUI_PATH=/path/to/ComfyUI
COMFYUI_PATH helps the exporter and generated scripts find the ComfyUI codebase. It does not, by itself, register this repo as a ComfyUI extension for the Web UI.
It also does not install ComfyUI runtime dependencies such as torch into the current Python environment.
COMFYUI_PATH is checked first. If it is not set, the exporter falls back to searching parent directories for a folder named ComfyUI.
If the CLI fails with ModuleNotFoundError: No module named 'torch', run the command with the same Python environment that launches ComfyUI, or install ComfyUI's runtime dependencies into the environment you are using for the CLI.
For Windows portable builds, run the CLI with ComfyUI's embedded Python from the extension directory:
..\..\..\python_embeded\python.exe -m comfyui_to_python --input_file ".\workflow_api.json" --output_file ".\workflow_api.py"
Web UI Export
In current ComfyUI builds, Save As Script is typically available under:
File -> Save As Script
The command downloads a generated .py file.
The current UI export uses the default filename workflow_api.py so it works in ComfyUI Desktop without relying on prompt().
Notes:
- menu placement can differ between frontend versions
- the Web UI export uses a fixed default filename rather than asking for one interactively
CLI Export
- In ComfyUI, enable dev mode options if needed.
- Save the workflow in API format:
File -> Export (API). - Run the exporter:
uv run python -m comfyui_to_python
Options:
uv run python -m comfyui_to_python \
--input_file workflow_api.json \
--output_file workflow_api.py \
--queue_size 10
The legacy wrapper still works if you prefer it:
uv run python comfyui_to_python.py
Flags:
--input_file: input workflow JSON, defaultworkflow_api.json--output_file: output Python file, defaultworkflow_api.py--queue_size: default execution count in the generated script, default10
Generated Scripts
Generated scripts depend on a working ComfyUI runtime.
If the repo is not inside ComfyUI, set:
export COMFYUI_PATH=/path/to/ComfyUI
The generated script is a workflow export. It does not automatically turn workflow inputs into command-line arguments.
Scripts exported directly from File -> Save As Script in the ComfyUI UI already include the frontend workflow metadata needed for drag-and-drop reimport. Images saved by those scripts can be dropped back into ComfyUI and reopen with the original workflow metadata.
Generated scripts reuse ComfyUI's runtime argument parser during bootstrap, so common ComfyUI memory flags such as --highvram, --normalvram, --lowvram, --novram, --cpu, and --disable-smart-memory can be passed directly to the exported .py file.
Lifecycle notes:
- exported scripts are single-shot workflow runners, not long-lived ComfyUI prompt servers
- they do not implement Web UI prompt/result caching across repeated service calls
- exported
main()now performs best-effort ComfyUI model/cache cleanup in afinallyblock - set
COMFYUI_TOPYTHON_UNLOAD_MODELS=1or callmain(unload_models=True)if an embedded or repeated-call host should aggressively unload models after each run instead of preserving them for reuse
Troubleshooting
- unsupported Python version: use Python 3.12 or newer in the environment that runs the extension, then reinstall the extension dependencies there
- Web UI import fails after
uv sync:uv syncin this repo installs dependencies into this repo's.venv, but ComfyUI loads custom nodes with its own Python environment. Install the extension into the Python interpreter that launches ComfyUI. - Windows portable import fails after
uv sync: ComfyUI portable uses its bundledpython_embededinterpreter. From the extension directory, run..\..\..\python_embeded\python.exe -m pip install -e ., then restart ComfyUI. - CLI fails with
No module named 'torch': the extension.venvmay not have ComfyUI's runtime dependencies. Either run the CLI from the Python environment that launches ComfyUI, or make sure the target ComfyUI environment is installed andCOMFYUI_PATHpoints to it. Save As Scriptnot visible: check your current ComfyUI menu/frontend version and look underFileSave As Scriptnot visible after restart: make sure this repo is discoverable by ComfyUI throughcustom_nodesby cloning it intoComfyUI/custom_nodes, symlinking it there, or adding an externalcustom_nodespath inextra_model_paths.yaml- save uses the default filename:
rename
workflow_api.pyafter download if you want a different local filename - ComfyUI cannot be found:
set
COMFYUI_PATH - models or paths are missing at runtime:
verify the target ComfyUI install and its
extra_model_paths.yaml

