Skip to content

Dependency Constraints

Override and control package versions globally across your environment using UV constraint dependencies.

Overview

Constraints are global version restrictions that override what versions UV can choose when resolving dependencies:

  • Pin specific versions - Lock PyTorch, NumPy, or other critical packages
  • Prevent updates - Keep packages within a major version range
  • Resolve conflicts - Force compatible versions when nodes disagree
  • Establish baselines - Set version requirements before adding nodes

Constraints are stored in [tool.uv.constraint-dependencies] in your .cec/pyproject.toml and apply to all dependencies in the environment.

Constraints vs regular dependencies

  • Regular dependencies (cfd py add) - Packages you explicitly need installed
  • Constraints (cfd constraint add) - Version restrictions without installation

Constraints don't install packages—they control what versions can be installed by other dependencies.

Before you begin

Make sure you have:

  • An active environment — cfd use <name> or use -e <name> flag
  • Understanding of version specifiers (==, >=, <, etc.)

Understanding constraints

What are constraints?

Think of constraints as meta-dependencies—they don't install packages themselves, but they restrict what versions UV can choose when resolving other dependencies.

Example scenario:

# Without constraints
cfd node add node-a  # Installs torch==2.0.0
cfd node add node-b  # Error! Requires torch>=2.1.0

# With constraints
cfd constraint add "torch==2.1.0"  # Set version requirement
cfd node add node-a  # Uses torch==2.1.0 (constraint overrides node's requirement)
cfd node add node-b  # Uses torch==2.1.0 (satisfied)

The constraint forces both nodes to use PyTorch 2.1.0, even though node-a requested 2.0.0.

How constraints work

When UV resolves dependencies:

  1. Reads constraints from [tool.uv.constraint-dependencies]
  2. Applies restrictions to all package resolution
  3. Rejects incompatible versions even if requested by dependencies
  4. Succeeds only if all constraints can be satisfied simultaneously

In pyproject.toml:

[tool.uv.constraint-dependencies]
torch = "==2.1.0"
numpy = ">=1.24.0"
pillow = ">=9.0.0,<10.0.0"

These constraints apply to: - Packages added with cfd py add - Dependencies from custom nodes (cfd node add) - Transitive dependencies (dependencies of dependencies)

Adding constraints

Add version restrictions to your environment:

cfd constraint add "torch==2.1.0"

What happens:

  1. Updates pyproject.toml - Adds to [tool.uv.constraint-dependencies]
  2. No immediate installation - Constraints don't install packages
  3. Applied on next resolution - Takes effect when you add/update packages

Example output:

📦 Adding constraints: torch==2.1.0

✓ Added 1 constraint(s) to pyproject.toml

Run 'comfydock -e my-env constraint list' to view all constraints

Adding multiple constraints

Add several constraints at once:

cfd constraint add "torch==2.1.0" "numpy>=1.24.0" "pillow<10.0"

All constraints are added together.

Common constraint patterns

Pin to exact version

Lock a package to a specific version:

cfd constraint add "torch==2.1.0"

Result: torch = "==2.1.0"

Use when: You need reproducibility or a specific feature version.

Minimum version requirement

Ensure at least a certain version:

cfd constraint add "numpy>=1.24.0"

Result: numpy = ">=1.24.0"

Use when: You need modern features or bug fixes.

Major version cap

Allow minor/patch updates but prevent major version changes:

cfd constraint add "pillow>=9.0.0,<10.0.0"

Result: pillow = ">=9.0.0,<10.0.0"

Use when: You want updates but need API stability.

Minor version cap

Lock to a specific minor version series:

cfd constraint add "transformers>=4.30.0,<4.31.0"

Result: transformers = ">=4.30.0,<4.31.0"

Use when: You need patch updates but want to avoid feature changes.

PyTorch with CUDA version

Pin PyTorch to a specific CUDA build:

cfd constraint add "torch==2.1.0+cu121"

Result: torch = "==2.1.0+cu121"

Use when: You need a specific CUDA version for GPU compatibility.

Quoting version specifiers

Always quote constraints with special characters:

cfd constraint add "numpy>=1.24"  # ✓ Correct
cfd constraint add numpy>=1.24    # ✗ Shell interprets >

Updating existing constraints

Adding a constraint for an existing package updates it:

# First time
cfd constraint add "torch==2.0.0"

# Update to newer version
cfd constraint add "torch==2.1.0"

The second command replaces the first constraint. No duplicates are created.

Listing constraints

View all active constraints in your environment:

cfd constraint list

Example output:

Constraint dependencies in 'my-project':
  • torch==2.1.0+cu121
  • numpy>=1.24.0
  • pillow>=9.0.0,<10.0.0
  • opencv-python>=4.8.0

When no constraints exist

cfd constraint list

Output:

No constraint dependencies configured

This is normal—most environments start without constraints and add them as needed.

Removing constraints

Remove version restrictions you no longer need:

cfd constraint remove torch

What happens:

  1. Removes from pyproject.toml - Deletes from [tool.uv.constraint-dependencies]
  2. Package stays installed - Doesn't uninstall the package
  3. Next resolution is unconstrained - Future updates can choose any version

Example output:

🗑 Removing constraints: torch

✓ Removed 1 constraint(s) from pyproject.toml

Removing multiple constraints

Remove several constraints at once:

cfd constraint remove torch numpy pillow

Removing non-existent constraints

ComfyDock safely handles constraints that don't exist:

cfd constraint remove nonexistent

Output:

🗑 Removing constraints: nonexistent
   Warning: constraint 'nonexistent' not found

✓ Removed 0 constraint(s) from pyproject.toml

No error—the operation is idempotent.

Common use cases

Resolving node conflicts

Two nodes require incompatible package versions:

# Check what's conflicting
cfd node add node-b

# Output:
# ✗ Dependency conflict:
#   torch==2.0.0 (required by node-a)
#   conflicts with torch>=2.1.0 (required by node-b)

# Add constraint to force compatible version
cfd constraint add "torch==2.1.0"

# Now both nodes can install
cfd node add node-b --yes

See Resolving Node Conflicts for detailed conflict resolution strategies.

Establishing a compatibility baseline

Set constraints before adding nodes to prevent conflicts:

# Set your environment's foundation
cfd constraint add "torch==2.1.0+cu121"
cfd constraint add "numpy>=1.24.0,<2.0.0"
cfd constraint add "pillow>=9.0.0,<10.0.0"

# Now add nodes—they'll all use compatible versions
cfd node add comfyui-impact-pack
cfd node add comfyui-controlnet-aux
cfd node add comfyui-animatediff

This prevents conflicts by establishing version requirements upfront.

Locking PyTorch backend

Pin PyTorch to a specific CUDA version for your GPU:

# CUDA 12.1
cfd constraint add "torch==2.1.0+cu121"
cfd constraint add "torchvision==0.16.0+cu121"
cfd constraint add "torchaudio==2.1.0+cu121"

# Or CUDA 11.8
cfd constraint add "torch==2.1.0+cu118"
cfd constraint add "torchvision==0.16.0+cu118"
cfd constraint add "torchaudio==2.1.0+cu118"

# Or CPU-only
cfd constraint add "torch==2.1.0+cpu"
cfd constraint add "torchvision==0.16.0+cpu"
cfd constraint add "torchaudio==2.1.0+cpu"

Ensures all nodes use the same PyTorch backend.

Preventing unwanted upgrades

Keep packages stable while allowing patch updates:

# Lock to transformers 4.30.x
cfd constraint add "transformers>=4.30.0,<4.31.0"

# Lock to diffusers 0.21.x
cfd constraint add "diffusers>=0.21.0,<0.22.0"

Useful when newer versions have breaking changes or regressions.

Forcing modern package versions

Ensure all dependencies use recent versions:

# Require Python 3.10+ features
cfd constraint add "numpy>=1.24.0"
cfd constraint add "scipy>=1.11.0"
cfd constraint add "pillow>=10.0.0"

Prevents nodes from dragging in old dependencies.

Working with local package indexes

Combine with custom indexes for air-gapped environments:

# Add constraint for package from custom index
cfd constraint add "internal-package>=1.0.0"

# The constraint applies when installing from custom index
cfd py add internal-package

Advanced patterns

Constraints from file

Create a constraints file for reusable baselines:

constraints.txt:

torch==2.1.0+cu121
torchvision==0.16.0+cu121
torchaudio==2.1.0+cu121
numpy>=1.24.0,<2.0.0
pillow>=9.0.0,<10.0.0
opencv-python>=4.8.0
transformers>=4.30.0,<5.0.0
diffusers>=0.21.0,<1.0.0

Apply all constraints:

# Read file and add each constraint
while IFS= read -r constraint; do
  [[ "$constraint" =~ ^#.*$ || -z "$constraint" ]] && continue
  cfd constraint add "$constraint"
done < constraints.txt

Or add manually:

cfd constraint add \
  "torch==2.1.0+cu121" \
  "numpy>=1.24.0,<2.0.0" \
  "pillow>=9.0.0,<10.0.0"

Environment-specific constraints

Different environments with different GPU capabilities:

# Development machine (CUDA 12.1)
cfd -e dev-env constraint add "torch==2.1.0+cu121"

# Production server (CUDA 11.8)
cfd -e prod-env constraint add "torch==2.1.0+cu118"

# CPU-only testing
cfd -e test-env constraint add "torch==2.1.0+cpu"

Temporary constraints for testing

Test if a version works before committing:

# Add constraint
cfd constraint add "experimental-pkg==0.1.0-beta"

# Test installation
cfd node add test-node

# If it doesn't work, remove constraint
cfd constraint remove experimental-pkg

# Try different version
cfd constraint add "experimental-pkg==0.0.9"

Constraints with node installation

Apply constraint only for specific node installation:

# Add constraint before node
cfd constraint add "torch>=2.1.0"
cfd node add cuda-heavy-node

# Remove constraint after (if not needed globally)
cfd constraint remove torch

Troubleshooting

Constraint blocks all installations

Error:

✗ Failed to add node
   No version of 'torch' satisfies constraint torch==2.0.0
   and requirement torch>=2.1.0 (from node-b)

Cause: Constraint is too restrictive for a dependency.

Solution:

  1. Check constraint:

    cfd constraint list
    

  2. Remove or relax constraint:

    cfd constraint remove torch
    # Or relax to range:
    cfd constraint add "torch>=2.0.0,<3.0.0"
    

  3. Retry installation:

    cfd node add node-b
    

Package still uses old version

Scenario: You added a constraint but package version didn't change.

Cause: Constraints don't trigger reinstallation—they only affect future resolutions.

Solution:

Force re-resolution with repair:

# View current state
cfd status

# Apply constraints by syncing environment
cfd repair --yes

This will sync packages to satisfy the new constraints.

Conflicting constraints

Error:

✗ Failed to resolve dependencies
   torch==2.0.0 conflicts with torch==2.1.0

Cause: You have multiple constraints for the same package with incompatible versions.

Solution:

  1. List constraints:

    cfd constraint list
    

  2. Identify duplicates or conflicts (shouldn't happen normally, but check)

  3. Remove and re-add with correct version:

    cfd constraint remove torch
    cfd constraint add "torch==2.1.0"
    

Constraint not taking effect

Scenario: Added constraint but node still installs different version.

Cause: Constraint syntax might be incorrect or package name mismatch.

Solution:

  1. Verify constraint was added:

    cfd constraint list
    

  2. Check package name spelling:

    # Wrong:
    cfd constraint add "pytorch==2.1.0"
    
    # Correct:
    cfd constraint add "torch==2.1.0"
    

  3. Check version specifier syntax:

    # Valid:
    cfd constraint add "numpy>=1.24.0"     # ✓
    cfd constraint add "pillow>=9.0,<10.0"  # ✓
    
    # Invalid:
    cfd constraint add "numpy>1.24"        # Missing .0
    cfd constraint add "pillow=>9.0"       # Wrong operator
    

Repair fails after adding constraint

Error:

✗ Failed to repair environment
   Could not resolve dependencies

Cause: Constraint is incompatible with existing dependencies.

Solution:

  1. Check what's installed:

    cfd py list --all
    

  2. Remove conflicting constraint:

    cfd constraint remove problematic-package
    

  3. Try repair again:

    cfd repair --yes
    

  4. If still fails, check node conflicts:

    cfd status
    

See Node Conflicts for detailed resolution steps.

How it works

Behind the scenes

When you run cfd constraint add:

  1. Reads pyproject.toml - Loads current configuration
  2. Updates or adds constraint - Modifies [tool.uv.constraint-dependencies]
  3. Writes to disk - Saves changes to .cec/pyproject.toml
  4. Tracks in git - Changes ready to commit

Example pyproject.toml:

[tool.uv.constraint-dependencies]
torch = "==2.1.0+cu121"
numpy = ">=1.24.0,<2.0.0"
pillow = ">=9.0.0,<10.0.0"

Constraints vs dependencies

Dependencies ([project.dependencies]): - Install packages in your environment - Show up in cfd py list - Required for your code to run - Added with cfd py add

Constraints ([tool.uv.constraint-dependencies]): - Restrict versions during resolution - Don't install anything themselves - Applied to all dependencies - Added with cfd constraint add

Example showing both:

[project.dependencies]
# These packages ARE installed
requests = ">=2.28.0"

[tool.uv.constraint-dependencies]
# These are version restrictions applied during resolution
urllib3 = ">=1.26.0,<2.0.0"  # Constrains requests' dependency

Even though you didn't explicitly add urllib3 as a dependency, the constraint affects what version requests can use for its urllib3 dependency.

UV resolution with constraints

UV's resolution algorithm:

  1. Collects requirements from:
  2. [project.dependencies]
  3. Node groups ([project.optional-dependencies])
  4. Transitive dependencies

  5. Applies constraints from [tool.uv.constraint-dependencies]

  6. Finds compatible versions that satisfy all requirements + constraints

  7. Fails if impossible to satisfy everything simultaneously

Example:

[project.dependencies]
node-a-dep = "*"  # Wants torch>=2.0.0

[project.optional-dependencies]
"node/node-b" = ["torch>=2.1.0"]

[tool.uv.constraint-dependencies]
torch = "==2.1.0"

Resolution: - node-a-dep wants torch>=2.0.02.1.0 satisfies - node-b wants torch>=2.1.02.1.0 satisfies - Constraint forces torch==2.1.0 → Final version: 2.1.0

Relationship to repair

The cfd repair command re-syncs your environment to match pyproject.toml:

# Add constraint
cfd constraint add "torch==2.1.0"

# Constraint is in pyproject.toml but not applied yet

# Apply constraint by repairing
cfd repair --yes

This triggers UV to re-resolve with the new constraint and install the correct versions.

Best practices

Start broad, narrow as needed

Begin with minimal constraints:

# Start with no constraints
cfd node add node-a node-b node-c

# Only add constraints when conflicts arise
# (ComfyDock will tell you if there's a conflict)

Add constraints reactively rather than preemptively.

Use version ranges over exact pins

Prefer ranges for flexibility:

# More flexible (allows patch updates)
cfd constraint add "numpy>=1.24.0,<2.0.0"

# Less flexible (locked to exact version)
cfd constraint add "numpy==1.24.3"

Use exact pins only when necessary (PyTorch CUDA versions, known bugs).

Document your constraints

Add comments to your pyproject.toml explaining why:

[tool.uv.constraint-dependencies]
# Pin PyTorch for CUDA 12.1 compatibility with RTX 4090
torch = "==2.1.0+cu121"

# Prevent numpy 2.0 due to breaking changes in node-x
numpy = ">=1.24.0,<2.0.0"

# Lock pillow to v9 due to regression in v10 loading certain formats
pillow = ">=9.5.0,<10.0.0"

Helps future you understand the reasoning.

Keep constraints in version control

Commit constraint changes with descriptive messages:

# Add constraint
cfd constraint add "torch==2.1.0+cu121"

# Commit with context
cfd commit -m "Pin PyTorch to 2.1.0+cu121 for CUDA 12.1 support"

Review constraints periodically

Constraints can become outdated:

# List all constraints
cfd constraint list

# Remove ones that are no longer needed
cfd constraint remove old-package

# Update versions to more recent ranges
cfd constraint add "numpy>=1.26.0,<2.0.0"

Test after changing constraints

Verify environment still works:

# Change constraint
cfd constraint add "torch==2.2.0"

# Apply changes
cfd repair --yes

# Test ComfyUI starts
cfd run

Catch issues early before committing.

Next steps