Skip to content

Updater

updater

ComfyUI Update System.

Migrated to uv — no raw pip. Handles: - ComfyUI core update (git pull) - Custom nodes update via manifest - Python dependencies update - Triton / SageAttention re-install

update_comfyui_core(comfy_path, log)

Update ComfyUI core via git pull.

Source code in src/installer/updater.py
def update_comfyui_core(comfy_path: Path, log: InstallerLogger) -> None:
    """Update ComfyUI core via git pull."""
    log.step("Updating ComfyUI Core")

    if not comfy_path.exists():
        log.error("ComfyUI directory not found!")
        return

    log.item("Pulling latest changes...")
    try:
        run_and_log("git", ["-C", str(comfy_path), "pull", "--ff-only"])
        log.sub("ComfyUI updated.", style="success")
    except CommandError:
        log.warning("Git pull failed. You may have local changes.", level=2)
        log.info("Try: git -C ComfyUI stash && git -C ComfyUI pull")

update_custom_nodes(python_exe, comfy_path, install_path, log, *, node_tier='full')

Update bundled custom nodes. User-installed nodes are NEVER touched.

Merges new nodes from the source manifest (additive-only) so that user customizations are preserved while newly added nodes are picked up on every update.

Source code in src/installer/updater.py
def update_custom_nodes(
    python_exe: Path,
    comfy_path: Path,
    install_path: Path,
    log: InstallerLogger,
    *,
    node_tier: str = "full",
) -> None:
    """Update bundled custom nodes. User-installed nodes are NEVER touched.

    Merges new nodes from the source manifest (additive-only) so that
    user customizations are preserved while newly added nodes are
    picked up on every update.
    """

    from src.installer.environment import find_source_scripts
    from src.installer.nodes import load_manifest, update_all_nodes

    scripts_dir = install_path / "scripts"
    manifest_path = scripts_dir / "custom_nodes.json"

    # Merge new nodes from source (additive-only — never overwrites user changes)
    source_dir = find_source_scripts()
    if source_dir:
        source_manifest = source_dir / "custom_nodes.json"
        if source_manifest.exists():
            scripts_dir.mkdir(parents=True, exist_ok=True)
            added = _merge_node_manifests(source_manifest, manifest_path, log)
            if added > 0:
                log.item(f"custom_nodes.json: {added} new node(s) added from source.", style="cyan")
            else:
                log.sub("custom_nodes.json is up to date.", style="success")

    if not manifest_path.exists():
        log.skip_step("Custom Nodes — manifest not found")
        return

    custom_nodes_dir = comfy_path / "custom_nodes"
    manifest = load_manifest(manifest_path)

    # Filter by tier so only the selected bundle is installed
    from src.installer.nodes import filter_by_tier
    manifest = filter_by_tier(manifest, node_tier)

    update_all_nodes(manifest, custom_nodes_dir, python_exe, log)

    # Provision nunchaku_versions.json during updates as well
    # (Handles Docker environments where nodes are skipped during initial build)
    nunchaku_src = scripts_dir / "nunchaku_versions.json"
    if not nunchaku_src.exists() and source_dir:
        nunchaku_src = source_dir / "nunchaku_versions.json"

    nunchaku_dst = custom_nodes_dir / "ComfyUI-nunchaku" / "nunchaku_versions.json"
    if nunchaku_src.exists() and nunchaku_dst.parent.exists():
        import shutil
        shutil.copy2(nunchaku_src, nunchaku_dst)
        log.sub("  nunchaku_versions.json provisioned.", style="success")

update_dependencies(python_exe, comfy_path, install_path, log)

Update Python dependencies via uv.

Source code in src/installer/updater.py
def update_dependencies(
    python_exe: Path,
    comfy_path: Path,
    install_path: Path,
    log: InstallerLogger,
) -> None:
    """Update Python dependencies via uv."""
    log.step("Updating Python Dependencies")

    deps_file = install_path / "scripts" / "dependencies.json"
    if not deps_file.exists():
        log.warning("dependencies.json not found. Skipping.", level=1)
        return

    deps = load_dependencies(deps_file)

    # Update ComfyUI requirements
    req_file = comfy_path / deps.pip_packages.comfyui_requirements
    if req_file.exists():
        log.item("Updating ComfyUI requirements...")
        uv_install(python_exe, requirements=req_file, upgrade=True)

    # Detect CUDA tag (used by standard packages, wheels, and optional torch update)
    from src.installer.optimizations import _get_cuda_version_from_torch

    cuda_ver = _get_cuda_version_from_torch(python_exe)
    cuda_tag: str | None = None
    if cuda_ver:
        try:
            parts = cuda_ver.split(".")
            from src.utils.gpu import cuda_tag_from_version
            cuda_tag = cuda_tag_from_version((int(parts[0]), int(parts[1])))
        except (ValueError, IndexError):
            pass

    if cuda_tag is None:
        # On macOS, keep None — there is no CUDA at all
        import sys
        if sys.platform != "darwin":
            supported = deps.pip_packages.supported_cuda_tags
            cuda_tag = supported[0] if supported else "cu130"

    # Install/update standard packages (insightface, facexlib, etc.)
    from src.installer.dependencies import install_python_packages, install_wheels
    install_python_packages(python_exe, deps, log, cuda_tag=cuda_tag)

    # Install/update wheel packages (nunchaku)
    install_wheels(python_exe, install_path, deps, log, cuda_tag=cuda_tag)

    # Update torch
    if confirm("Update PyTorch? (Only if there's a new CUDA version)"):
        torch_cfg = deps.pip_packages.get_torch(cuda_tag)
        if torch_cfg:
            torch_pkgs = torch_cfg.packages.split()
            log.item(f"Updating PyTorch [{cuda_tag}]...")
            uv_install(
                python_exe,
                torch_pkgs,
                index_url=torch_cfg.index_url,
                upgrade=True,
            )
        else:
            log.warning(f"No PyTorch config for '{cuda_tag}'. Skipping torch update.", level=1)

run_update(install_path, *, verbose=False, node_tier='full')

Run the full update process.

Parameters:

Name Type Description Default
install_path Path

Root installation directory.

required
verbose bool

Show detailed subprocess output.

False
node_tier str

Custom nodes bundle tier — 'minimal', 'umeairt', or 'full'.

'full'
Source code in src/installer/updater.py
def run_update(install_path: Path, *, verbose: bool = False, node_tier: str = "full") -> None:
    """
    Run the full update process.

    Args:
        install_path: Root installation directory.
        verbose: Show detailed subprocess output.
        node_tier: Custom nodes bundle tier — 'minimal', 'umeairt', or 'full'.
    """
    log = setup_logger(
        log_file=install_path / "logs" / "update_log.txt",
        total_steps=6,
        verbose=verbose,
    )
    log.banner("UmeAiRT", "ComfyUI — Updater", __version__)

    comfy_path = install_path / "ComfyUI"
    scripts_dir = install_path / "scripts"

    # Detect python executable
    python_exe = _detect_python(scripts_dir, log)

    # Run update steps
    update_comfyui_core(comfy_path, log)
    update_custom_nodes(python_exe, comfy_path, install_path, log, node_tier=node_tier)
    update_dependencies(python_exe, comfy_path, install_path, log)

    # Model security scan (non-blocking)
    _scan_models_warning(install_path, log)

    # Install/update GPU optimizations (SageAttention, Triton, Flash-Attn)
    _install_optimizations(python_exe, comfy_path, install_path, log)

    log.step("Update Complete!")
    log.success("All components have been updated.", level=1)