Skip to content
Merged

v2.16.4 #1742

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e52c45
Merge pull request #1379 from ionite34/fix/package-delete-guards
mohnjiles Sep 2, 2026
1f2a5de
Fix chagenlog merge
mohnjiles Sep 2, 2026
e53cd4d
Merge pull request #1381 from ionite34/backport/main/pr-1379
mohnjiles Sep 2, 2026
55b2871
Merge pull request #1385 from ionite34/fix/workflow-link-cycle
mohnjiles Sep 9, 2026
39d6358
Merge pull request #1386 from ionite34/backport/main/pr-1385
mohnjiles Sep 9, 2026
0358978
Merge LykosAI/StabilityMatrix main into downmerg-from-public-1738
mohnjiles Sep 13, 2026
9865fc9
chore: changelog + format for public downmerge
mohnjiles Sep 13, 2026
b697386
Merge pull request #1388 from ionite34/downmerg-from-public-1738
mohnjiles Sep 13, 2026
a329ba9
chore: add v2.16.4 Supporters shoutout
mohnjiles Sep 13, 2026
079dfea
Merge pull request #1392 from ionite34/chore/supporters-2.16.4
mohnjiles Sep 14, 2026
0b96de7
Merge pull request #1393 from ionite34/fix/wan2gp-gradio-error-class
mohnjiles Sep 14, 2026
4081cfa
fix chagenlog
mohnjiles Sep 14, 2026
f9b560d
Merge pull request #1394 from ionite34/backport/main/pr-1393
mohnjiles Sep 14, 2026
93f097f
Merge pull request #1395 from ionite34/docs-everyday-guides
mohnjiles Sep 14, 2026
9ea3469
Merge pull request #1398 from ionite34/backport/main/pr-1395
mohnjiles Sep 14, 2026
f576f8f
docs: add v2.16.4 technical release notes
mohnjiles Sep 16, 2026
f787bfb
Merge pull request #1401 from ionite34/notes-2.16.4
mohnjiles Sep 16, 2026
e29da47
Merge pull request #1400 from ionite34/fix/env-var-user-override
ionite34 Sep 16, 2026
0989017
Fix merge for v2.16.4
ionite34 Sep 16, 2026
a66eebb
Fix whitespace
ionite34 Sep 16, 2026
d903227
Merge pull request #1402 from ionite34/backport/main/pr-1400
ionite34 Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).

## v2.16.4
### Added
- Added **Comfy Kitchen Attention** (`--use-ck-attention`) as a **Cross Attention Method** launch option for ComfyUI and ComfyUI-Zluda - thanks to @e-nord!
### Changed
- **Windows ROCm** PyTorch installs now come from AMD's new permanent ROCm 10 package repositories, avoiding the `CUDA error: invalid argument` / `hipErrorInvalidValue` failures reported with the ROCm 7.14.1 builds — packages already on 7.14.1 need a reinstall, or an update that also upgrades PyTorch (like ComfyUI's) - thanks to @NeuralFault!
### Fixed
- Installing a package into a folder that already contains files — like an existing ComfyUI installation placed in `Data\Packages` to be imported — now shows the folder's total size and file count and requires explicit confirmation, instead of silently deleting everything in it; this includes the first-run one-click installer ([#1733](https://github.com/LykosAI/StabilityMatrix/issues/1733))
- Turning off shared model folders no longer tries to delete real directories at the link locations — only links created by Stability Matrix are removed, so the model folders of an imported package are never touched ([#1733](https://github.com/LykosAI/StabilityMatrix/issues/1733))
- The package uninstall confirmation now correctly lists models/checkpoints among the items that will be deleted for packages using config-based model sharing (ComfyUI's default) — previously it implied they were safe — and shows the package folder's total size
- Fixed the **Workflows** page loading forever (and eventually freezing the app) when the workflow library folder is itself a link to ComfyUI's `user\default\workflows` folder — the `Stability Matrix` link ComfyUI gets at launch pointed back into the same folder, nesting `Stability Matrix\Stability Matrix\...` thousands of levels deep. That link is no longer created (and an existing one is removed the next time ComfyUI is launched) when it would loop, and the workflow and model folder scans now follow linked folders at most once
- Fixed the **Cross Attention Method** launch options for ComfyUI and ComfyUI-Zluda letting you enable several at once — they're mutually exclusive, so they're now radio buttons - thanks to @e-nord!
- Fixed [#1725](https://github.com/LykosAI/StabilityMatrix/issues/1725) - **AI-Toolkit** jobs crashing with a `distutils` error on installs from before v2.16.3 that still run Python 3.11, no reinstall needed - thanks to @NeuralFault!
- Fixed Wan2GP v13's **Deepy** panel repeatedly showing **Connection to server lost** and logging `issubclass() arg 1 must be a class` — Stability Matrix's console logging wrapper now preserves Gradio's exception class ([Wan2GP #2298](https://github.com/deepbeepmeep/Wan2GP/issues/2298))
- Fixed Wan2GP (and AI Toolkit's SageAttention source build) crashing with `ModuleNotFoundError: No module named 'distutils'` on Python 3.12+ — forcing `SETUPTOOLS_USE_DISTUTILS=stdlib` unconditionally broke on versions where distutils was removed from the standard library entirely, it's now version-gated the same way AI Toolkit's own fix already was
- Environment Variables configured in **Settings** now correctly override the workaround variables (like `SETUPTOOLS_USE_DISTUTILS`) that some packages set internally
### Supporters
#### 🌟 Visionaries
Small release, big priority: 2.16.4 is mostly about keeping your files exactly where you left them. Imported ComfyUI folders can't be wiped by surprise anymore, and a looping workflow link can't freeze the app. That kind of careful, unflashy work only gets our full attention because of our Visionaries, so thank you **Waterclouds**, **MrMxyzptlk12836**, **bluepopsicle**, **Ibixat**, **Droolguy**, **snotty**, **dispenser**, **cusalapapen1481**, **moon_milky2843**, **sn3232323233350**, **CC**, **TwistedDragon**, **hasezou11013179**, and **Akros**. Every folder that stays put has a bit of you in it. 💛
#### 🚀 Pioneers
To our Pioneers **SinthCore**, **Jisuren**, **jweg79**, **Hurbie53**, **Cjloha**, **Alligator1907**, **CommissarGiygas16050**, **bastardofbethlehem**, **Zombop**, **Silerae**, **joshsciascia72**, and **cy_hart691842**: patch releases like this one land quietly, and you back them anyway. We never take that for granted. And hello to **okachako**, our newest Pioneer; it's so nice to see your name here! 💛

## v2.16.3
### Added
#### New Feature: 🔀 CivitAI Workflow Browsing
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@

Multi-Platform Package Manager and Inference UI for Stable Diffusion

See the [documentation index](docs/README.md) for installation, package management, models, and Inference guides. Start with [your first image](docs/inference/text-to-image.md), [importing local models](docs/checkpoint-manager/overview.md#import-local-models), or [managing installed packages](docs/package-manager/managing-packages.md).

### 🖱️ One click install and update for Stable Diffusion Web UI Packages
- Supports:
- [Stable Diffusion WebUI reForge][reforge], [Stable Diffusion WebUI Forge][forge], [Stable Diffusion WebUI AMDGPU Forge][amdforge] [Automatic 1111][auto1111], [Automatic 1111 DirectML][auto1111-directml], [SD Web UI-UX][webui-ux], [SD.Next][sdnext]
Expand Down
79 changes: 79 additions & 0 deletions StabilityMatrix.Avalonia/DialogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
Expand All @@ -20,19 +22,25 @@
using AvaloniaEdit.TextMate;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using Refit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Helpers;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Services;
using TextMateSharp.Grammars;
using Process = FuzzySharp.Process;
using Size = StabilityMatrix.Core.Helper.Size;

namespace StabilityMatrix.Avalonia;

Expand Down Expand Up @@ -638,6 +646,77 @@ public static TaskDialog CreateTaskDialog(string title, string description)
XamlRoot = App.VisualRoot,
};
}

/// <summary>
/// Shows a confirmation dialog before deleting existing files at a package install location,
/// e.g. a folder the user placed there manually to import an existing installation.
/// Returns true if the location does not exist, is empty, or the user explicitly confirmed
/// the deletion; false if the user cancelled.
/// </summary>
public static async Task<bool> ConfirmDeleteExistingInstallDataAsync(DirectoryPath installLocation)
{
if (!installLocation.Exists || !installLocation.Info.EnumerateFileSystemInfos().Any())
return true;

var vmFactory = App.Services.GetRequiredService<IServiceManager<ViewModelBase>>();

var vm = vmFactory.Get<ConfirmDeleteDialogViewModel>();
vm.Title = "Existing files found";
vm.Description =
"This folder already exists and contains files that were not installed by Stability Matrix. "
+ "Installing here will permanently delete the folder shown below and everything inside it.\n\n"
+ "If this is an existing installation you want to keep, choose Cancel and use Import "
+ "from the Packages page instead, or move your data elsewhere first.";
vm.PathsToDelete = [installLocation.FullPath];
vm.IsRecycleBinAvailable = false;
vm.TotalSizeText = "Calculating size…";

Task.Run(() =>
{
var (fileCount, totalBytes) = GetFileStats(installLocation.Info);
Dispatcher.UIThread.Post(() =>
vm.TotalSizeText =
$"Total: {Size.FormatBytes(Convert.ToUInt64(totalBytes))} in {fileCount:N0} files"
);
})
.SafeFireAndForget(ex =>
{
Logger.Warn(ex, "Failed to calculate size of {Path}", installLocation.FullPath);
Dispatcher.UIThread.Post(() => vm.TotalSizeText = null);
});

return await vm.GetDialog().ShowAsync() == ContentDialogResult.Primary;
}

/// <summary>
/// Gets the total file count and size of a directory, without following symbolic links / junctions.
/// </summary>
private static (int FileCount, long TotalBytes) GetFileStats(DirectoryInfo directory)
{
var fileCount = 0;
var totalBytes = 0L;

foreach (var file in directory.EnumerateFiles())
{
if (file.Attributes.HasFlag(FileAttributes.ReparsePoint))
continue;

fileCount++;
totalBytes += file.Length;
}

foreach (var subDir in directory.EnumerateDirectories())
{
if (subDir.Attributes.HasFlag(FileAttributes.ReparsePoint))
continue;

var (subCount, subBytes) = GetFileStats(subDir);
fileCount += subCount;
totalBytes += subBytes;
}

return (fileCount, totalBytes);
}
}

// Text fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ public partial class ConfirmDeleteDialogViewModel(ILogger<ConfirmDeleteDialogVie
[ObservableProperty]
private string title = "Confirm Delete";

/// <summary>
/// Optional description shown below the title, for extra context on what is being deleted and why.
/// </summary>
[ObservableProperty]
private string? description;

/// <summary>
/// Optional total size / file count line shown below the paths list.
/// </summary>
[ObservableProperty]
private string? totalSizeText;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ConfirmDeleteButtonText))]
[NotifyPropertyChangedFor(nameof(IsPermanentDelete))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;

namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;

Expand All @@ -21,6 +23,9 @@ public partial class ConfirmPackageDeleteDialogViewModel : ContentDialogViewMode
[NotifyPropertyChangedFor(nameof(IsValid))]
public partial string PackageName { get; set; } = string.Empty;

[ObservableProperty]
public partial string? FolderSizeText { get; set; }

public string? ExpectedPackageName => Package.DisplayName;
public bool IsValid => ExpectedPackageName?.Equals(PackageName, StringComparison.Ordinal) ?? false;
public string DeleteWarningText
Expand All @@ -36,7 +41,9 @@ public string DeleteWarningText
if (!Package.UseSharedOutputFolder)
items.Add("• Images/outputs");

if (Package.PreferredSharedFolderMethod is SharedFolderMethod.None)
// Symlink mode is the only one that relocates models out of the package folder;
// in Configuration (yaml) and None modes they are real files that will be deleted
if (Package.PreferredSharedFolderMethod is not SharedFolderMethod.Symlink)
items.Add("• Models/checkpoints placed in the package's model folders");

items.Add("• Any custom files in the package folder");
Expand All @@ -50,4 +57,23 @@ private async Task CopyExpectedPackageName()
{
await App.Clipboard?.SetTextAsync(ExpectedPackageName);
}

/// <inheritdoc />
public override async Task OnLoadedAsync()
{
await base.OnLoadedAsync();

if (Package.FullPath is not { } fullPath)
return;

try
{
var sizeBytes = await new DirectoryPath(fullPath).GetSizeAsync(includeSymbolicLinks: false);
FolderSizeText = $"Total size: {Size.FormatBytes(Convert.ToUInt64(sizeBytes))}";
}
catch (Exception)
{
// Size display is informational only
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
Expand Down Expand Up @@ -121,9 +122,49 @@ private async Task InstallComfyForInference()
[RelayCommand]
private async Task InstallPackage(BasePackage selectedPackage)
{
OnPrimaryButtonClick();

var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", selectedPackage.Name);
var installPath = new DirectoryPath(installLocation);

// In the first-run flow the progress dialog is opened when the recommended-models dialog
// closes; the existing-data path skips that dialog, so it opens the progress dialog itself
var showProgressDialogOnStart = false;

if (installPath.Exists && installPath.Info.EnumerateFileSystemInfos().Any())
{
// The install location already holds data we didn't put there — likely an existing
// installation the user placed for Import. Close this dialog first so the
// confirmation is the only dialog on screen, then require explicit confirmation
// before deleting anything.
OnCloseButtonClick();

if (!await DialogHelper.ConfirmDeleteExistingInstallDataAsync(installPath))
{
notificationService.Show(
"Installation cancelled",
$"Existing files at {installLocation} were left untouched.",
NotificationType.Information
);
return;
}

await installPath.DeleteVerboseAsync(logger);

// The Packages page indexed the staged folder as an unknown package; re-index now
// that it's gone so the stale card doesn't linger during the install
EventManager.Instance.OnInstalledPackagesChanged();

showProgressDialogOnStart = true;
}
else
{
OnPrimaryButtonClick();

if (installPath.Exists)
{
await installPath.DeleteVerboseAsync(logger);
}
}

var recommendedPython = selectedPackage.RecommendedPythonVersion;

var steps = new List<IPackageStep>
Expand All @@ -132,13 +173,6 @@ private async Task InstallPackage(BasePackage selectedPackage)
new SetupPrerequisitesStep(prerequisiteHelper, selectedPackage, recommendedPython),
};

// get latest version & download & install
if (Directory.Exists(installLocation))
{
var installPath = new DirectoryPath(installLocation);
await installPath.DeleteVerboseAsync(logger);
}

var downloadVersion = await selectedPackage.GetLatestVersion();
var installedVersion = new InstalledPackageVersion { IsPrerelease = false };

Expand Down Expand Up @@ -208,7 +242,7 @@ private async Task InstallPackage(BasePackage selectedPackage)

var runner = new PackageModificationRunner
{
ShowDialogOnStart = false,
ShowDialogOnStart = showProgressDialogOnStart,
HideCloseButton = false,
ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,7 @@ private async Task LoadInstalledWorkflowsAsync()
var count = 0;

foreach (
var workflowPath in Directory.EnumerateFiles(
settingsManager.WorkflowDirectory,
"*.json",
EnumerationOptionConstants.AllDirectories
)
var workflowPath in LinkSafeFileSystem.EnumerateFiles(settingsManager.WorkflowDirectory, "*.json")
)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1143,8 +1143,7 @@ partial void OnIsSharedModelSymlinkChanged(bool oldValue, bool newValue)
}
else
{
var basePackage = packageFactory[Package!.PackageName!];
basePackage!.RemoveModelFolderLinks(Package.FullPath!, SharedFolderMethod.Symlink);
RemoveModelFolderLinks(SharedFolderMethod.Symlink);
}
}

Expand All @@ -1166,11 +1165,31 @@ partial void OnIsSharedModelConfigChanged(bool oldValue, bool newValue)
}
else
{
var basePackage = packageFactory[Package!.PackageName!];
basePackage!.RemoveModelFolderLinks(Package.FullPath!, SharedFolderMethod.Configuration);
RemoveModelFolderLinks(SharedFolderMethod.Configuration);
}
}

private void RemoveModelFolderLinks(SharedFolderMethod sharedFolderMethod)
{
var basePackage = packageFactory[Package!.PackageName!];
basePackage!
.RemoveModelFolderLinks(Package.FullPath!, sharedFolderMethod)
.SafeFireAndForget(ex =>
{
logger.LogError(
ex,
"Failed to remove {Method} model folder links for {Package}",
sharedFolderMethod,
Package.DisplayName
);
notificationService.Show(
"Failed to remove shared model links",
ex.Message,
NotificationType.Error
);
});
}

partial void OnIsSharedModelDisabledChanged(bool value)
{
if (value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,15 @@ x.PackageName is nameof(ComfyUI) or "ComfyUI-Zluda"
InstallName = InstallName.Trim();

var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", InstallName);
if (Directory.Exists(installLocation))
var installPath = new DirectoryPath(installLocation);

// Require explicit confirmation before deleting an existing non-empty folder at the
// install location — it may be a manually placed installation the user meant to Import
if (!await DialogHelper.ConfirmDeleteExistingInstallDataAsync(installPath))
return;

if (installPath.Exists)
{
var installPath = new DirectoryPath(installLocation);
await installPath.DeleteVerboseAsync(logger);
}

Expand Down
Loading
Loading