Skip to content

v2.16.2 Technical Notes

Extended technical notes for the v2.16.2 release — the deeper "how and why" behind each change. For the user-facing summary, see the CHANGELOG.

Home

Table of Contents

Mask Editor and Annotation Editor Stability

Random crashes while drawing or saving. Canvas rendering runs on a separate render thread, while undo/redo, layer operations, exporting, and closing the editor could free the graphics resources a frame was still drawing with — occasionally crashing the app mid-stroke or while saving. The canvas threading model was redesigned so this can't happen structurally: the render thread now exclusively owns the on-screen graphics resources, exports composite from immutable snapshots on their own surfaces, in-progress strokes hand the renderer stable point snapshots, and closing the editor waits for the in-flight frame before freeing anything.

Pen pressure. Pressing harder mid-stroke re-widened the entire stroke while drawing (the width was a running average recomputed every frame), and after saving and reopening a project, mouse-drawn strokes came back roughly 25% thicker as full-pressure pen strokes. Pressure now stays per-segment while drawing, and mouse strokes keep their original width across save/load. Existing project files load exactly as before.

Graphics memory leaks. Neither editor released its paint canvas on close, and paint-bucket fill results were never freed at all — each fill pinned a full-canvas bitmap until app exit.

Performance. Dragging an image layer with the Move tool now re-renders only the dragged layer instead of re-compositing every layer on each pointer move. Color-mask extraction for regional prompting was rewritten — a 1024×1024 canvas with six regions previously did over six million dictionary lookups per extraction. A small native leak (one path object per frame while drawing with the mouse) was fixed, and a redundant full-canvas scan after every paint-bucket fill was removed.

CivitAI File Types and Install Detection

Empty Files sections. CivitAI expanded its file type list; files with the newer labels (Diffusion Model, UNet, Text Encoder, and others) deserialized to an internal Unknown type, and the browser filtered file lists by Type == Model — so official releases like Krea 2 Turbo and Z-Image showed an empty Files pane with no download links. All current CivitAI file types are now recognized, and the canonical type list is pinned by a test so a future addition fails the build loudly instead of silently hiding files.

"Installed" detection for Unknown-typed files (#1679). The install-state checks gated on the file type being recognized model weights before ever looking at the hash, so Unknown-typed files never showed the Installed label or version checkmark despite being downloaded and indexed. Detection is now purely blake3-hash-based: a file whose hash is in the local model index is on disk, regardless of what type CivitAI reports. Version delete uses the same rule, so Unknown-typed files are cleaned up too.

Download destinations. Downloads now route by the file's declared type first (Diffusion Model/UNet → DiffusionModels, Text Encoder → TextEncoders, CLIP Vision → ClipVision, ControlNet → ControlNet, Upscaler → upscalers), with the older name/base-model heuristics kept only for files still typed plain "Model". External stabilitymatrix:// download links use the same logic as the in-app browser.

GGUF Text Encoders

Loading — the Text Encoder dropdowns listed .gguf files, but every CLIP call site wired them into the standard CLIPLoader/DualCLIPLoader nodes, which ComfyUI rejects. Typed nodes for the ComfyUI-GGUF loaders (CLIPLoaderGGUF, DualCLIPLoaderGGUF, TripleCLIPLoaderGGUF, QuadrupleCLIPLoaderGGUF) were added — verified against the extension's source: the single and dual variants take a type input, the triple and quadruple do not. Any selection containing a .gguf encoder routes through the GGUF loader; since those loaders also read .safetensors, mixed GGUF + safetensors selections work. This covers all Inference workflows (single through quadruple encoder setups, Wan, custom UNet) and the Image Lab providers.

Dropdown visibility while connected — the connected-mode text encoder list was replaced wholesale by the names ComfyUI reports, and core ComfyUI never lists .gguf files in the text encoder folder (only the GGUF extension's loader nodes do). So a GGUF encoder in the shared TextEncoders folder vanished from the dropdown the moment ComfyUI connected. The list is now built like the checkpoint list: local index entries first (they carry richer metadata), then server-reported names not already covered.

HuggingFace browser destinations — the folder guesser had a blanket ".gguf → DiffusionModels" rule, so text encoder GGUFs were suggested into the UNet folder. It now recognizes more encoder name patterns (byt5/mt5/llama/gemma/qwen2 prefixes, text-encoder/enconly names) before that fallback.

Chroma and Single-Encoder CLIP Types

ComfyUI's single CLIPLoader and DualCLIPLoader accept different type lists — notably, flux is dual-only (it loads a clip_l + t5xxl pair), while chroma is the single-encoder type for Chroma's lone t5xxl. The Inference encoder Type dropdown was missing chroma and several other single-encoder types (wan, qwen_image, omnigen2, mochi, pixart, cosmos, stable_cascade, stable_audio), so the closest available option demanded a second encoder these models don't use. The missing types are now listed, and the default encoder slot count comes from one shared mapping: dual-loader types get a pair, sd3/hidream get three/four, and every other type gets a single slot.

Required-Extension Checks

FaceDetailer duplicate-key error (#1681). The pre-generation extension check builds a dictionary of installed ComfyUI extensions keyed by git remote URL. Two custom_nodes folders reporting the same remote — for example a stray ComfyUI clone inside custom_nodes — made that dictionary construction throw "An item with the same key has already been added" before generation started. FaceDetailer merely activates this code path via its required-extension declarations; the fix de-duplicates by URL (case-insensitively, since git hosting URLs are case-insensitive).

Image Lab (#1672). Image Lab queued workflows without checking their declared required extensions, so a GGUF model without ComfyUI-GGUF installed surfaced as a generic "ComfyUI rejected the workflow" 400. It now performs the same pre-queue check as Inference and offers the same one-click install-and-restart dialog; the flow is shared between both features.

Package Manager Fixes

Package import misclassification (#1667). The import dialog performed no package-type detection — it silently pre-selected the first entry in the difficulty-sorted list, a Forge variant whose launch command is launch.py. Re-importing packages after settings loss could therefore record everything (including ComfyUI) as Forge. The dialog now reads the folder's .git remote origin URL and matches it against known packages, with the checked-out branch as a tie-break for packages sharing one repository (Forge Classic vs Neo).

reForge CUDA-on-AMD (#1669). reForge pins torch==2.9.0 to match upstream, but the rocm7.2 wheel index hosts no torch 2.9.x at all — and because the index is passed via --extra-index-url, pip kept PyPI in play and quietly resolved the CUDA wheel instead. reForge now installs from rocm6.4, which hosts 2.9.0+rocm6.4 (verified against the index); Forge itself keeps rocm7.2.

Output Browser crash (#1668). A broken junction or symlink under an output folder — a reparse point whose target no longer exists — threw DirectoryNotFoundException mid-enumeration while building the folder tree, which is why manually re-creating the missing folder didn't help. Inaccessible entries are now skipped with a logged warning.

Interpreter probe hardening. The sitecustomize.py shipped into package venvs now routes its error messages to stderr, so interpreter probes and pip list output parsing can't be corrupted by them (follow-up to the #1620/#1643 startup hardening; the underlying trigger is security software such as ESET interfering with the embedded Python).

Linux AppImage

FUSE3 runtime (#1682). The old build pipeline embedded a FUSE2-based AppImage runtime requiring libfuse.so.2, which modern distros (Ubuntu 24.04+, Fedora 40+) no longer ship. The build was upgraded to PupNet 1.9.1 with the modern type2-runtime, which is statically linked against libfuse3, accepts either fusermount or fusermount3 at runtime, and falls back to --appimage-extract-and-run when FUSE isn't available at all. Most desktop users need nothing; minimal systems may need sudo apt install fuse3 or equivalent. Contributed by @NeuralFault.

Desktop entry (#1666). AppImage runs previously wrote a .desktop entry with NoDisplay=true and no icon, so Stability Matrix never appeared in the application launcher, and the file was rewritten (reverting manual edits) on every exit. Runs now install a correct entry with the extracted app icon and report a matching WM_CLASS, so the running window shows the proper icon in the dock/taskbar. Only applies to AppImage runs; deb/rpm/flatpak installs keep their package-managed entries.

Deep links. stabilitymatrix:// URIs (for example CivitAI's "Download with Stability Matrix" buttons) were ignored on Linux: Windows registers the scheme handler with a --uri argument, but the Linux .desktop handler passes the URI as a bare positional argument (%u). Both forms are now accepted and forwarded to the running instance.

Windows ROCm

bitsandbytes wheel. The Windows ROCm helper's bundled bitsandbytes wheel was updated to a build compatible with ROCm 7.13–7.15, keeping pace with AMD's ROCm Technical Preview builds. Contributed by @NeuralFault.

OneTrainer. OneTrainer joined the Windows ROCm helper: new installs on supported AMD hardware get the ROCm Technical Preview PyTorch build via the shared install path, a ROCm-aware bitsandbytes wheel plus triton-windows, and the helper's hardware-appropriate launch environment applied automatically. Upstream OneTrainer also renamed its UI script (train_ui.pytrain_ui_ctk.py) — the launch command was updated to match, and new installs default to Python 3.12 on all platforms. Contributed by @NeuralFault.