- Add UnifiedDepResolver module with 7 FRs: collect, compile, install pipeline - Integrate startup batch resolution in prestartup_script.py (module scope) - Skip per-node pip install in execute_install_script() when unified mode active - Add use_unified_resolver config flag following use_uv pattern - Input sanitization: reject -r, -e, --find-links, @ file://, path separators - Handle --index-url/--extra-index-url separation with credential redaction - Fallback to per-node pip on resolver failure or uv unavailability - Add 98 unit tests across 20 test classes - Add PRD and Design docs with cm_global integration marked as DEFERRED
19 KiB
PRD: Unified Dependency Resolver
1. Overview
1.1 Background
ComfyUI Manager currently installs each node pack's requirements.txt individually via pip install.
This approach causes dependency conflicts where installing a new node pack can break previously installed node packs' dependencies.
Current flow:
graph LR
A1[Install node pack A] --> A2[pip install A's deps] --> A3[Run install.py]
B1[Install node pack B] --> B2[pip install B's deps] --> B3[Run install.py]
B2 -.->|May break<br/>A's deps| A2
1.2 Goal
Implement a unified dependency installation module that uses uv to resolve all dependencies (installed node packs + new node packs) at once.
New flow (unified resolver mode):
graph TD
subgraph "Install Time (immediate)"
A1[User installs node pack X] --> A2[Git clone / download]
A2 --> A3["Run X's install.py (if exists)"]
A3 --> A4["Skip per-node pip install<br/>(deps deferred to restart)"]
end
subgraph "ComfyUI Restart (startup batch)"
B1[prestartup_script.py] --> B2[Collect ALL installed node packs' deps]
B2 --> B3["uv pip compile → pinned requirements.txt"]
B3 --> B4["uv pip install -r → Batch install"]
B4 --> B5[PIPFixer environment correction]
end
Terminology: In this document, "lockfile" refers to the pinned requirements.txt generated by
uv pip compile. This is different from theuv.lock(TOML format) generated byuv lock. We use a pip-compatible workflow.
1.3 Scope
- Develop a new dedicated dependency resolution module
- Opt-in activation from the existing install process
- Handles dependency resolution (deps install) only.
install.pyexecution is handled by existing logic
2. Constraints
| Item | Description |
|---|---|
| uv required | Only operates in environments where uv is available |
Independent of use_uv flag |
use_unified_resolver is separate from the existing use_uv flag. Even if use_uv=False, setting use_unified_resolver=True attempts resolver activation. Auto-fallback if uv is not installed |
| Pre-validated list | Input node pack list is assumed to be pre-verified for mutual dependency compatibility |
| Backward compatibility | Existing pip-based install process is fully preserved (fallback) |
| Blacklist/overrides bypassed | In unified mode, pip_blacklist, pip_overrides, pip_downgrade_blacklist are NOT applied (empty values passed). Constructor interface is preserved for future extensibility. uv pip compile handles all conflict resolution natively. [DEFERRED] Reading actual values from cm_global at startup is deferred to a future version — v1 always passes empty values |
| Multiple custom_nodes paths | Supports all paths returned by folder_paths.get_folder_paths('custom_nodes') |
| Scope of application | Batch resolver runs at module scope in prestartup_script.py (unconditionally when enabled, independent of install-scripts.txt existence). The 2 execute_install_script() locations skip per-node pip install when unified mode is active (deps deferred to restart). execute_lazy_install_script() is also modified to skip per-node pip install in unified mode. Other install paths such as install_manager_requirements(), pip_install() are outside v1 scope (future extension) |
| Legacy module | comfyui_manager/legacy/manager_core.py is excluded from modification. Legacy paths retain existing pip behavior |
3. Functional Requirements
FR-1: Node Pack List and Base Dependency Input
Input:
- Node pack list (fullpath list of installed + to-be-installed node packs)
- Base dependencies (ComfyUI's
requirements.txtandmanager_requirements.txt)
Behavior:
- Validate each node pack path
- Exclude disabled (
.disabled) node packs- Detection criteria: Existence of
custom_nodes/.disabled/{node_pack_name}directory - Existing mechanism: Disabling a node pack moves it from
custom_nodes/tocustom_nodes/.disabled/(does NOT create a.disabledfile inside the node pack) - At resolver input time, disabled node packs should already be absent from
custom_nodes/, so normally they won't be innode_pack_paths - Defensively exclude any node pack paths that are within the
.disableddirectory
- Detection criteria: Existence of
- Base dependencies are treated as constraints
- Traverse all paths from
folder_paths.get_folder_paths('custom_nodes')
cm_global runtime dependencies:
cm_global.pip_overrides,pip_blacklist,pip_downgrade_blacklistare dynamically assigned duringprestartup_script.pyexecution- In unified mode, these are not applied — empty values are passed to the resolver constructor
- The constructor interface accepts these parameters for future extensibility (defaults to empty when
None)
FR-2: Dependency List Extraction
Behavior:
- Parse
requirements.txtfrom each node pack directory- Encoding: Use
robust_readlines()pattern (chardetdetection, assumes UTF-8 if not installed)
- Encoding: Use
- Package name remapping (constructor accepts
overridesdict — empty in v1, interface preserved for extensibility) - Blacklist package filtering (constructor accepts
blacklistset — empty in v1, uv handles torch etc. natively) - Downgrade blacklist filtering (constructor accepts
downgrade_blacklistlist — empty in v1) - Note: In unified mode,
uv pip compileresolves all version conflicts natively. The blacklist/overrides/downgrade_blacklist mechanisms from the existing pip flow are bypassed - Strip comments (
#) and blank lines - Input sanitization (see below)
- Separate handling of
--index-urlentries (see below)
Input sanitization:
- Requirements lines matching the following patterns are rejected and logged (security defense):
-r,--requirement(recursive include → path traversal risk)-e,--editable(VCS/local path install → arbitrary code execution risk)-c,--constraint(external constraint file injection)--find-links,-f(external package source specification)@ file://(local file reference → path traversal risk)- Package names containing path separators (
/,\)
- Allowed items: Package specs (
name>=version), specs with--index-url, environment markers (containing;) - Rejected lines are recorded in the
skippedlist with reason
--index-url handling:
- Existing code (standalone function
execute_install_script()) parsespackage_name --index-url URLformat for special handling - Note: The class method
UnifiedManager.execute_install_script()does NOT have this handling (asymmetric) - The unified resolver unifies both paths for consistent handling:
- Package spec → added to the general dependency list
--extra-index-url URL→ passed asuv pip compileargument
- Separated index URLs are collected in
CollectedDeps.extra_index_urls - Credential redaction: Authentication info (
user:pass@) in index URLs is masked during logging
Duplicate handling strategy:
- No deduplication is performed directly
- Different version specs of the same package are all passed as-is to uv
uv pip compilehandles version resolution (uv determines the optimal version)
Output:
- Unified dependency list (tracked by source node pack)
- Additional index URL list
FR-3: uv pip compile Execution
Behavior:
- Generate temporary requirements file from the collected dependency list
- Execute
uv pip compileto produce a pinned requirements.txt--output-file(required): Specify output file (outputs to stdout only if not specified)--constraint: Pass base dependencies as constraints--python: Current Python interpreter path--extra-index-url: Additional index URLs collected from FR-2 (multiple allowed)
- Resolve for the current platform (platform-specific results)
Error handling:
- Return conflict package report when resolution fails
- Timeout handling (300s): Explicitly catch
subprocess.TimeoutExpired, terminate child process, then fallback - Lockfile output file existence verification: Confirm file was actually created even when
returncode == 0 - Temp file cleanup: Guaranteed in
finallyblock. Includes stale temp file cleanup logic at next execution for abnormal termination (SIGKILL) scenarios
Output:
- pinned requirements.txt (file with all packages pinned to exact versions)
FR-4: Pinned Requirements-based Dependency Installation
Behavior:
- Execute
uv pip install -r <pinned-requirements.txt>- Do NOT use
uv pip sync: sync deletes packages not in the lockfile, risking removal of torch, ComfyUI's own dependencies, etc.
- Do NOT use
- Already-installed packages at the same version are skipped (default uv behavior)
- Log installation results
Error handling:
uv pip install -ris an atomic operation (all-or-nothing)- On total failure: Parse stderr for failure cause report → fallback to existing pip
- No partial failure report (not possible due to uv's behavior)
InstallResult'sinstalled/skippedfields are populated by parsing uv stdout;stderrrecords failure cause (no separatefailedfield needed due to atomic model)
FR-5: Post-install Environment Correction
Behavior:
- Call
PIPFixer.fix_broken()for environment integrity correction- Restore torch version (when change detected)
- Fix OpenCV conflicts
- Restore comfyui-frontend-package
- Restore packages based on
pip_auto_fix.list
- This step is already performed in the existing
execute_install_script()flow, so the unified resolver itself doesn't need to call it- However, an optional call option is provided for cases where the resolver is invoked independently outside the existing flow
FR-6: install.py Execution (Existing Flow Maintained)
Behavior:
- The unified resolver handles deps installation at startup time only
install.pyexecution is handled by the existingexecute_install_script()flow and runs immediately at install time- Deps are deferred to startup batch resolution;
install.pyruns without waiting for deps
Control flow specification (unified mode active):
execute_install_script(): skip therequirements.txt-based individual pip install loop entirely (deps will be resolved at next restart)install.pyexecution runs immediately as before- At next ComfyUI restart:
prestartup_script.pyruns the unified resolver for all installed node packs
Control flow specification (unified mode inactive / fallback):
- Existing pip install loop runs as-is (no change)
install.pyexecution runs immediately as before
FR-7: Startup Batch Resolution
Behavior:
- When
use_unified_resolver=True, all dependency resolution is deferred to ComfyUI startup - At install time: node pack itself is installed (git clone, etc.) and
install.pyruns immediately, butrequirements.txtdeps are not installed per-request - At startup time:
prestartup_script.pyruns the unified resolver once for all installed node packs
Startup execution flow (in prestartup_script.py):
- At module scope (before
execute_startup_script()gate): checkmanager_util.use_unified_resolverflag - If enabled: collect all installed node pack paths, read base requirements from
comfy_path - Create
UnifiedDepResolverwith empty blacklist/overrides/downgrade_blacklist (uv handles resolution natively) - Call
resolve_and_install()— collects all deps → compile → install in one batch - On success: set
_unified_resolver_succeeded = True, skip per-node pip inexecute_lazy_install_script() - On failure: log warning,
execute_lazy_install_script()falls back to existing per-node pip install - Note: Runs unconditionally when enabled, independent of
install-scripts.txtexistence
execute_install_script() behavior in unified mode:
- Skip the
requirements.txtpip install loop entirely (deps will be handled at restart) install.pyexecution still runs immediately
execute_lazy_install_script() behavior in unified mode:
- Skip the
requirements.txtpip install loop (already handled by startup batch resolver) install.pyexecution still runs
Windows-specific behavior:
- Windows lazy install path also benefits from startup batch resolution
try_install_script()defers toreserve_script()as before for non-instant_execution=Trueinstalls
4. Non-functional Requirements
| Item | Requirement |
|---|---|
| Performance | Equal to or faster than existing individual installs |
| Stability | Must not break the existing environment |
| Logging | Log progress and results at each step (details below) |
| Error recovery | Fallback to existing pip method on failure |
| Testing | Unit test coverage above 80% |
| Security | requirements.txt input sanitization (see FR-2), credential log redaction, subprocess list-form invocation |
| Concurrency | Prevent lockfile path collisions on concurrent install requests. Use process/thread-unique suffixes or temp directories |
| Temp files | Guarantee temp file cleanup on both normal and abnormal termination. Clean stale files on next execution |
Logging Requirements
| Step | Log Level | Content |
|---|---|---|
| Resolver start | INFO |
Node pack count, total dependency count, mode (unified/pip) |
| Dependency collection | INFO |
Collection summary (collected N, skipped N, sources N) |
| Dependency collection | DEBUG |
Per-package collection/skip/remap details |
--index-url detection |
INFO |
Detected additional index URL list |
| uv compile start | INFO |
Execution command (excluding sensitive info) |
| uv compile success | INFO |
Pinned package count, elapsed time |
| uv compile failure | WARNING |
Conflict details, fallback transition notice |
| Install start | INFO |
Number of packages to install |
| Install success | INFO |
Installed/skipped/failed count summary, elapsed time |
| Install failure | WARNING |
Failed package list, fallback transition notice |
| Fallback transition | WARNING |
Transition reason, original error message |
| Overall completion | INFO |
Final result summary (success/fallback/failure) |
Log prefix: All logs use
[UnifiedDepResolver]prefix to distinguish from existing pip install logs
5. Usage Scenarios
Scenario 1: Single Node Pack Installation (unified mode)
User requests installation of node pack X
→ Git clone / download node pack X
→ Run X's install.py (if exists) — immediately
→ Skip per-node pip install (deps deferred)
→ User restarts ComfyUI
→ prestartup_script.py: Collect deps from ALL installed node packs (A,B,C,X)
→ uv pip compile resolves fully compatible versions
→ uv pip install -r for batch installation
→ PIPFixer environment correction
Scenario 2: Multi Node Pack Batch Installation (unified mode)
User requests installation of node packs X, Y, Z
→ Each node pack: git clone + install.py — immediately
→ Per-node pip install skipped for all
→ User restarts ComfyUI
→ prestartup_script.py: Collect deps from ALL installed node packs (including X,Y,Z)
→ Single uv pip compile → single uv pip install -r
→ PIPFixer environment correction
Scenario 3: Dependency Resolution Failure (Edge Case)
Even pre-validated lists may fail due to uv version differences or platform issues
→ uv pip compile failure → return conflict report
→ Display conflict details to user
→ Auto-execute existing pip fallback
Scenario 4: uv Not Installed
uv unavailable detected → auto-fallback to existing pip method
→ Display uv installation recommendation to user
Scenario 5: Windows Lazy Installation (unified mode)
Node pack installation requested on Windows
→ Node pack install deferred to startup (existing lazy mechanism)
→ On next ComfyUI startup: unified resolver runs first (batch deps)
→ execute_lazy_install_script() skips per-node pip (already resolved)
→ install.py still runs per node pack
Scenario 6: Malicious/Non-standard requirements.txt
Node pack's requirements.txt contains `-r ../../../etc/hosts` or `-e git+https://...`
→ Sanitization filter rejects the line
→ Log rejection reason and continue processing remaining valid packages
→ Notify user of rejected item count
Scenario 7: Concurrent Install Requests (unified mode)
User requests installation of node packs A and B nearly simultaneously from UI
→ Each request: git clone + install.py immediately, deps skipped
→ On restart: single unified resolver run handles both A and B deps together
→ No concurrency issue (single batch at startup)
6. Success Metrics
| Metric | Target |
|---|---|
| Dependency conflict reduction | 90%+ reduction compared to current |
| Install success rate | 99%+ (for compatibility-verified lists) |
| Performance | Equal to or better than existing individual installs |
| Adoption rate | 50%+ of eligible users |
7. Future Extensions
cm_globalintegration [DEFERRED]: Readpip_blacklist,pip_overrides,pip_downgrade_blacklistfromcm_globalruntime values instead of passing empty. Constructor interface already accepts these parameters- Lockfile caching: Reuse for identical node pack configurations
- Pre-install dependency conflict validation API: Check compatibility before installation
- Dependency tree visualization: Display dependency relationships to users
uv lock-based cross-platform lockfile support (TOML format)install_manager_requirements()integration: Resolve manager's own dependencies through unified resolverpip_install()integration: Route UI direct installs through unified resolver- Legacy module (
comfyui_manager/legacy/) unified resolver support
Appendix A: Existing Code Install Path Mapping
This section is reference material to clarify the unified resolver's scope of application.
| Install Path | Location | v1 Applied | Notes |
|---|---|---|---|
UnifiedManager.execute_install_script() |
glob/manager_core.py (method) |
✅ Yes | Skips per-node pip in unified mode (deps deferred to restart) |
Standalone execute_install_script() |
glob/manager_core.py (function) |
✅ Yes | Skips per-node pip in unified mode (deps deferred to restart) |
execute_lazy_install_script() |
prestartup_script.py |
✅ Yes | Skips per-node pip in unified mode (already batch-resolved) |
| Startup batch resolver | prestartup_script.py |
✅ Yes | New: Runs unified resolver once at startup for all node packs |
install_manager_requirements() |
glob/manager_core.py |
❌ No | Manager's own deps |
pip_install() |
glob/manager_core.py |
❌ No | UI direct install |
Legacy execute_install_script() (2 locations) |
legacy/manager_core.py |
❌ No | Legacy paths |