From b42b449fb74ac37804f0bca90ff7944ad79c069c Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Thu, 15 Jan 2026 13:12:04 -0800 Subject: [PATCH 01/10] Fix venv troubleshooting package check --- src/virtualEnvironment.ts | 6 +++--- tests/unit/virtualEnvironment.test.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/virtualEnvironment.ts b/src/virtualEnvironment.ts index c626cc04c..f9262dc4c 100644 --- a/src/virtualEnvironment.ts +++ b/src/virtualEnvironment.ts @@ -889,7 +889,7 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { * * Parses the text output of `uv pip install --dry-run -r requirements.txt`. * @returns `'OK'` if pip install does not detect any missing packages, - * `'manager-upgrade'` if `uv` and `toml` are missing, + * `'package-upgrade'` if only known upgrade packages are missing, * or `'error'` when any other combination of packages are missing. */ async hasRequirements(): Promise<'OK' | 'error' | 'package-upgrade'> { @@ -973,13 +973,13 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { } if (!coreOk || !managerOk) { - log.info('Requirements are out of date. Treating as package upgrade.', { + log.info('Requirements missing beyond known upgrade cases.', { coreOk, managerOk, upgradeCore, upgradeManager, }); - return 'package-upgrade'; + return 'error'; } if (await this.needsNvidiaTorchUpgrade()) { diff --git a/tests/unit/virtualEnvironment.test.ts b/tests/unit/virtualEnvironment.test.ts index 2e6af3a4d..cfc3bd4e4 100644 --- a/tests/unit/virtualEnvironment.test.ts +++ b/tests/unit/virtualEnvironment.test.ts @@ -188,13 +188,13 @@ describe('VirtualEnvironment', () => { expect(log.info).toHaveBeenCalledWith(expect.stringContaining('pip install --dry-run -r')); }); - test('returns package-upgrade when packages are missing and not a known upgrade case', async ({ virtualEnv }) => { + test('returns error when packages are missing and not a known upgrade case', async ({ virtualEnv }) => { mockSpawnOutputOnce(' + unknown_package==1.0.0\n'); mockSpawnOutputOnce('Would make no changes\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('package-upgrade'); + await expect(virtualEnv.hasRequirements()).resolves.toBe('error'); expect(log.info).toHaveBeenCalledWith( - expect.stringContaining('Requirements are out of date. Treating as package upgrade.'), + expect.stringContaining('Requirements missing beyond known upgrade cases.'), expect.objectContaining({ coreOk: false, managerOk: true, upgradeCore: false, upgradeManager: false }) ); }); @@ -263,7 +263,7 @@ describe('VirtualEnvironment', () => { mockSpawnOutputOnce(' - unknown-package==1.0.0\n + aiohttp==3.9.0\n', 0, null); mockSpawnOutputOnce('Would make no changes\n', 0, null); - await expect(virtualEnv.hasRequirements()).resolves.toBe('package-upgrade'); + await expect(virtualEnv.hasRequirements()).resolves.toBe('error'); }); }); From e687fdc171a7db4c378dc9ec0ff7e6d8ef8c7a2d Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Thu, 15 Jan 2026 14:29:39 -0800 Subject: [PATCH 02/10] Clarify requirements check status --- src/main-process/comfyInstallation.ts | 8 ++++--- src/virtualEnvironment.ts | 23 ++++++++++++-------- tests/unit/virtualEnvironment.test.ts | 30 +++++++++++++-------------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main-process/comfyInstallation.ts b/src/main-process/comfyInstallation.ts index d2e5e9627..4b7bfab77 100644 --- a/src/main-process/comfyInstallation.ts +++ b/src/main-process/comfyInstallation.ts @@ -184,12 +184,14 @@ export class ComfyInstallation { // Python packages try { const result = await venv.hasRequirements(); - if (result === 'package-upgrade') { + if (result.status === 'upgrade') { validation.pythonPackages = 'OK'; validation.upgradePackages = 'warning'; + } else if (result.status === 'ok') { + validation.pythonPackages = 'OK'; } else { - validation.pythonPackages = result; - if (result !== 'OK') log.error('Virtual environment is incomplete.'); + validation.pythonPackages = 'error'; + log.error('Virtual environment is incomplete.', { reason: result.reason }); } } catch (error) { log.error('Failed to read venv packages.', error); diff --git a/src/virtualEnvironment.ts b/src/virtualEnvironment.ts index f9262dc4c..9f3a291df 100644 --- a/src/virtualEnvironment.ts +++ b/src/virtualEnvironment.ts @@ -58,6 +58,13 @@ type TorchPackageVersions = Record; const TORCH_PACKAGE_NAMES: TorchPackageName[] = ['torch', 'torchaudio', 'torchvision']; +export type RequirementsCheckStatus = 'ok' | 'missing' | 'upgrade' | 'error'; + +export type RequirementsCheckResult = { + status: RequirementsCheckStatus; + reason?: string; +}; + export function getPipInstallArgs(config: PipInstallConfig): string[] { const installArgs = ['pip', 'install']; @@ -327,7 +334,7 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { const requirementsStatus = await this.hasRequirements(); - if (requirementsStatus === 'OK') { + if (requirementsStatus.status === 'ok') { log.info('Skipping requirements installation - all requirements already installed'); } else { log.info('Starting manual install - venv missing requirements'); @@ -888,11 +895,9 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { * Checks if the virtual environment has all the required packages of ComfyUI core. * * Parses the text output of `uv pip install --dry-run -r requirements.txt`. - * @returns `'OK'` if pip install does not detect any missing packages, - * `'package-upgrade'` if only known upgrade packages are missing, - * or `'error'` when any other combination of packages are missing. + * @returns Result describing whether requirements are satisfied, missing, or require a known upgrade path. */ - async hasRequirements(): Promise<'OK' | 'error' | 'package-upgrade'> { + async hasRequirements(): Promise { const checkRequirements = async (requirementsPath: string) => { const args = ['pip', 'install', '--dry-run', '-r', requirementsPath]; log.info(`Running uv command directly: ${args.join(' ')}`); @@ -969,7 +974,7 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { if ((managerOk && upgradeCore) || (coreOk && upgradeManager) || (upgradeCore && upgradeManager)) { log.info('Package update of known packages required. Core:', upgradeCore, 'Manager:', upgradeManager); - return 'package-upgrade'; + return { status: 'upgrade', reason: 'known-packages' }; } if (!coreOk || !managerOk) { @@ -979,16 +984,16 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { upgradeCore, upgradeManager, }); - return 'error'; + return { status: 'missing', reason: 'unknown-packages' }; } if (await this.needsNvidiaTorchUpgrade()) { log.info('NVIDIA PyTorch version out of date. Treating as package upgrade.'); - return 'package-upgrade'; + return { status: 'upgrade', reason: 'nvidia-torch' }; } log.debug('hasRequirements result:', 'OK'); - return 'OK'; + return { status: 'ok' }; } /** diff --git a/tests/unit/virtualEnvironment.test.ts b/tests/unit/virtualEnvironment.test.ts index cfc3bd4e4..5d110f14b 100644 --- a/tests/unit/virtualEnvironment.test.ts +++ b/tests/unit/virtualEnvironment.test.ts @@ -146,8 +146,8 @@ test.for(allCombinations)('hasRequirements', async ({ core, manager }, { virtual mockSpawnForPackages(core); mockSpawnForPackages(manager); - const result = core.length + manager.length === 0 ? 'OK' : 'package-upgrade'; - await expect(virtualEnv.hasRequirements()).resolves.toBe(result); + const result = core.length + manager.length === 0 ? 'ok' : 'upgrade'; + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: result }); expect(log.info).toHaveBeenCalledWith(expect.stringContaining('pip install --dry-run -r')); }); @@ -184,26 +184,26 @@ describe('VirtualEnvironment', () => { mockSpawnOutputOnce('Would make no changes\n'); mockSpawnOutputOnce('Would make no changes\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('OK'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'ok' }); expect(log.info).toHaveBeenCalledWith(expect.stringContaining('pip install --dry-run -r')); }); - test('returns error when packages are missing and not a known upgrade case', async ({ virtualEnv }) => { + test('returns missing when packages are missing and not a known upgrade case', async ({ virtualEnv }) => { mockSpawnOutputOnce(' + unknown_package==1.0.0\n'); mockSpawnOutputOnce('Would make no changes\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('error'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'missing' }); expect(log.info).toHaveBeenCalledWith( expect.stringContaining('Requirements missing beyond known upgrade cases.'), expect.objectContaining({ coreOk: false, managerOk: true, upgradeCore: false, upgradeManager: false }) ); }); - test('returns package-upgrade for manager upgrade case', async ({ virtualEnv }) => { + test('returns upgrade for manager upgrade case', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would make no changes\n'); mockSpawnOutputOnce('Would install 1 package \n + chardet==5.2.0\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('package-upgrade'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); expect(log.info).toHaveBeenCalledWith( 'Package update of known packages required. Core:', false, @@ -212,11 +212,11 @@ describe('VirtualEnvironment', () => { ); }); - test('returns package-upgrade for manager upgrade case', async ({ virtualEnv }) => { + test('returns upgrade for manager upgrade case', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would make no changes\n'); mockSpawnOutputOnce('Would install 2 packages \n + uv==1.0.0 \n + toml==1.0.0\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('package-upgrade'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); expect(log.info).toHaveBeenCalledWith( 'Package update of known packages required. Core:', false, @@ -225,19 +225,19 @@ describe('VirtualEnvironment', () => { ); }); - test('returns package-upgrade for core + manager upgrade case', async ({ virtualEnv }) => { + test('returns upgrade for core + manager upgrade case', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would install 3 packages \n + av==1.0.0 \n + yarl==12.0.8 \n + aiohttp==3.9.0\n'); mockSpawnOutputOnce('Would install 2 packages \n + uv==1.0.0 \n + toml==1.0.0\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('package-upgrade'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); expect(log.info).toHaveBeenCalledWith('Package update of known packages required. Core:', true, 'Manager:', true); }); - test('returns package-upgrade for core upgrade case', async ({ virtualEnv }) => { + test('returns upgrade for core upgrade case', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would install 1 package \n + av==1.0.0\n'); mockSpawnOutputOnce('Would make no changes\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('package-upgrade'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); }); test('throws error when pip command fails', async ({ virtualEnv }) => { @@ -256,14 +256,14 @@ describe('VirtualEnvironment', () => { mockSpawnOutputOnce('', 0, null, 'Would make no changes\n'); mockSpawnOutputOnce('', 0, null, 'Would make no changes\n'); - await expect(virtualEnv.hasRequirements()).resolves.toBe('OK'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'ok' }); }); test('rejects core upgrade with unrecognized package removal', async ({ virtualEnv }) => { mockSpawnOutputOnce(' - unknown-package==1.0.0\n + aiohttp==3.9.0\n', 0, null); mockSpawnOutputOnce('Would make no changes\n', 0, null); - await expect(virtualEnv.hasRequirements()).resolves.toBe('error'); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'missing' }); }); }); From dcfe1af029e96f8a667dfe94e375ae7899f1f072 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Thu, 15 Jan 2026 14:55:52 -0800 Subject: [PATCH 03/10] Fix installation manager test for new requirements result --- tests/unit/install/installationManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/install/installationManager.test.ts b/tests/unit/install/installationManager.test.ts index af9f92969..a7d71a70a 100644 --- a/tests/unit/install/installationManager.test.ts +++ b/tests/unit/install/installationManager.test.ts @@ -86,7 +86,7 @@ vi.mock('@/virtualEnvironment', () => { return { VirtualEnvironment: vi.fn(() => ({ exists: vi.fn(() => Promise.resolve(true)), - hasRequirements: vi.fn(() => Promise.resolve(true)), + hasRequirements: vi.fn(() => Promise.resolve({ status: 'ok' })), pythonInterpreterPath: 'valid/python', uvPath: 'valid/uv', venvPath: 'valid/venv', From 48d38e23b75a9c23ba35cb39e8e3c1c17b4974b1 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Thu, 15 Jan 2026 14:58:34 -0800 Subject: [PATCH 04/10] Constrain requirements reason and clarify test name --- src/virtualEnvironment.ts | 4 +++- tests/unit/virtualEnvironment.test.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/virtualEnvironment.ts b/src/virtualEnvironment.ts index 9f3a291df..f96ed32f7 100644 --- a/src/virtualEnvironment.ts +++ b/src/virtualEnvironment.ts @@ -60,9 +60,11 @@ const TORCH_PACKAGE_NAMES: TorchPackageName[] = ['torch', 'torchaudio', 'torchvi export type RequirementsCheckStatus = 'ok' | 'missing' | 'upgrade' | 'error'; +export type RequirementsCheckReason = 'known-packages' | 'unknown-packages' | 'nvidia-torch'; + export type RequirementsCheckResult = { status: RequirementsCheckStatus; - reason?: string; + reason?: RequirementsCheckReason; }; export function getPipInstallArgs(config: PipInstallConfig): string[] { diff --git a/tests/unit/virtualEnvironment.test.ts b/tests/unit/virtualEnvironment.test.ts index 5d110f14b..9cc9aa8d3 100644 --- a/tests/unit/virtualEnvironment.test.ts +++ b/tests/unit/virtualEnvironment.test.ts @@ -199,7 +199,7 @@ describe('VirtualEnvironment', () => { ); }); - test('returns upgrade for manager upgrade case', async ({ virtualEnv }) => { + test('returns upgrade for manager upgrade case (uv/toml)', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would make no changes\n'); mockSpawnOutputOnce('Would install 1 package \n + chardet==5.2.0\n'); From d9ca6be01983b9da12b3ced2987da220dc05df6e Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 17 Jan 2026 13:03:03 -0800 Subject: [PATCH 05/10] Remove highly intelligent hardcoded allowlist prompting user "feature" --- src/main-process/comfyInstallation.ts | 16 ++++---- src/virtualEnvironment.ts | 56 +++------------------------ tests/unit/virtualEnvironment.test.ts | 37 +++++++++--------- 3 files changed, 32 insertions(+), 77 deletions(-) diff --git a/src/main-process/comfyInstallation.ts b/src/main-process/comfyInstallation.ts index 4b7bfab77..e2e50a337 100644 --- a/src/main-process/comfyInstallation.ts +++ b/src/main-process/comfyInstallation.ts @@ -184,14 +184,14 @@ export class ComfyInstallation { // Python packages try { const result = await venv.hasRequirements(); - if (result.status === 'upgrade') { - validation.pythonPackages = 'OK'; - validation.upgradePackages = 'warning'; - } else if (result.status === 'ok') { - validation.pythonPackages = 'OK'; - } else { - validation.pythonPackages = 'error'; - log.error('Virtual environment is incomplete.', { reason: result.reason }); + switch (result.status) { + case 'upgrade': + validation.pythonPackages = 'OK'; + validation.upgradePackages = 'warning'; + break; + case 'ok': + validation.pythonPackages = 'OK'; + break; } } catch (error) { log.error('Failed to read venv packages.', error); diff --git a/src/virtualEnvironment.ts b/src/virtualEnvironment.ts index f96ed32f7..b2701d5e7 100644 --- a/src/virtualEnvironment.ts +++ b/src/virtualEnvironment.ts @@ -58,9 +58,9 @@ type TorchPackageVersions = Record; const TORCH_PACKAGE_NAMES: TorchPackageName[] = ['torch', 'torchaudio', 'torchvision']; -export type RequirementsCheckStatus = 'ok' | 'missing' | 'upgrade' | 'error'; +export type RequirementsCheckStatus = 'ok' | 'upgrade'; -export type RequirementsCheckReason = 'known-packages' | 'unknown-packages' | 'nvidia-torch'; +export type RequirementsCheckReason = 'requirements-diff' | 'nvidia-torch'; export type RequirementsCheckResult = { status: RequirementsCheckStatus; @@ -897,7 +897,7 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { * Checks if the virtual environment has all the required packages of ComfyUI core. * * Parses the text output of `uv pip install --dry-run -r requirements.txt`. - * @returns Result describing whether requirements are satisfied, missing, or require a known upgrade path. + * @returns Result describing whether requirements are satisfied or require an upgrade. */ async hasRequirements(): Promise { const checkRequirements = async (requirementsPath: string) => { @@ -925,40 +925,6 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { return venvOk; }; - // Manager upgrade in 0.4.18 - uv, toml (exactly) - const isManagerUpgrade = (output: string) => { - // Match the original case: 2 packages (uv + toml) | Added in https://github.com/ltdrdata/ComfyUI-Manager/commit/816a53a7b1a057af373c458ebf80aaae565b996b - // Match the new case: 1 package (chardet) | Added in https://github.com/ltdrdata/ComfyUI-Manager/commit/60a5e4f2614c688b41a1ebaf0694953eb26db38a - const anyCombination = /\bWould install [1-3] packages?(\s+\+ (toml|uv|chardet)==[\d.]+){1,3}\s*$/; - return anyCombination.test(output); - }; - - // Package upgrade in 0.4.21 - aiohttp, av, yarl - const isCoreUpgrade = (output: string) => { - const lines = output.split('\n'); - let adds = 0; - for (const line of lines) { - // Reject upgrade if removing an unrecognised package - if ( - line.search( - /^\s*- (?!aiohttp|av|yarl|comfyui-workflow-templates|comfyui-embedded-docs|pydantic|pydantic-core|pydantic-settings|annotated-types|typing-inspection|alembic|sqlalchemy|greenlet|mako|python-dotenv).*==/ - ) !== -1 - ) - return false; - if (line.search(/^\s*\+ /) !== -1) { - if ( - line.search( - /^\s*\+ (aiohttp|av|yarl|comfyui-workflow-templates|comfyui-embedded-docs|pydantic|pydantic-core|pydantic-settings|annotated-types|typing-inspection|alembic|sqlalchemy|greenlet|mako|python-dotenv)==/ - ) === -1 - ) - return false; - adds++; - } - // An unexpected package means this is not a package upgrade - } - return adds > 0; - }; - const coreOutput = await checkRequirements(this.comfyUIRequirementsPath); if (!(await pathAccessible(this.comfyUIManagerRequirementsPath))) { throw new Error( @@ -971,22 +937,12 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { const coreOk = hasAllPackages(coreOutput); const managerOk = hasAllPackages(managerOutput); - const upgradeCore = !coreOk && isCoreUpgrade(coreOutput); - const upgradeManager = !managerOk && isManagerUpgrade(managerOutput); - - if ((managerOk && upgradeCore) || (coreOk && upgradeManager) || (upgradeCore && upgradeManager)) { - log.info('Package update of known packages required. Core:', upgradeCore, 'Manager:', upgradeManager); - return { status: 'upgrade', reason: 'known-packages' }; - } - if (!coreOk || !managerOk) { - log.info('Requirements missing beyond known upgrade cases.', { + log.info('Requirements out of date. Scheduling package update.', { coreOk, managerOk, - upgradeCore, - upgradeManager, }); - return { status: 'missing', reason: 'unknown-packages' }; + return { status: 'upgrade', reason: 'requirements-diff' }; } if (await this.needsNvidiaTorchUpgrade()) { @@ -994,7 +950,7 @@ export class VirtualEnvironment implements HasTelemetry, PythonExecutor { return { status: 'upgrade', reason: 'nvidia-torch' }; } - log.debug('hasRequirements result:', 'OK'); + log.debug('hasRequirements result:', 'ok'); return { status: 'ok' }; } diff --git a/tests/unit/virtualEnvironment.test.ts b/tests/unit/virtualEnvironment.test.ts index 9cc9aa8d3..7d0dcefea 100644 --- a/tests/unit/virtualEnvironment.test.ts +++ b/tests/unit/virtualEnvironment.test.ts @@ -188,52 +188,51 @@ describe('VirtualEnvironment', () => { expect(log.info).toHaveBeenCalledWith(expect.stringContaining('pip install --dry-run -r')); }); - test('returns missing when packages are missing and not a known upgrade case', async ({ virtualEnv }) => { + test('returns upgrade when packages are missing', async ({ virtualEnv }) => { mockSpawnOutputOnce(' + unknown_package==1.0.0\n'); mockSpawnOutputOnce('Would make no changes\n'); - await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'missing' }); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); expect(log.info).toHaveBeenCalledWith( - expect.stringContaining('Requirements missing beyond known upgrade cases.'), - expect.objectContaining({ coreOk: false, managerOk: true, upgradeCore: false, upgradeManager: false }) + expect.stringContaining('Requirements out of date. Scheduling package update.'), + expect.objectContaining({ coreOk: false, managerOk: true }) ); }); - test('returns upgrade for manager upgrade case (uv/toml)', async ({ virtualEnv }) => { + test('returns upgrade when manager requirements differ (single package)', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would make no changes\n'); mockSpawnOutputOnce('Would install 1 package \n + chardet==5.2.0\n'); await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); expect(log.info).toHaveBeenCalledWith( - 'Package update of known packages required. Core:', - false, - 'Manager:', - true + expect.stringContaining('Requirements out of date. Scheduling package update.'), + expect.objectContaining({ coreOk: true, managerOk: false }) ); }); - test('returns upgrade for manager upgrade case', async ({ virtualEnv }) => { + test('returns upgrade when manager requirements differ (multiple packages)', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would make no changes\n'); mockSpawnOutputOnce('Would install 2 packages \n + uv==1.0.0 \n + toml==1.0.0\n'); await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); expect(log.info).toHaveBeenCalledWith( - 'Package update of known packages required. Core:', - false, - 'Manager:', - true + expect.stringContaining('Requirements out of date. Scheduling package update.'), + expect.objectContaining({ coreOk: true, managerOk: false }) ); }); - test('returns upgrade for core + manager upgrade case', async ({ virtualEnv }) => { + test('returns upgrade when core and manager requirements differ', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would install 3 packages \n + av==1.0.0 \n + yarl==12.0.8 \n + aiohttp==3.9.0\n'); mockSpawnOutputOnce('Would install 2 packages \n + uv==1.0.0 \n + toml==1.0.0\n'); await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); - expect(log.info).toHaveBeenCalledWith('Package update of known packages required. Core:', true, 'Manager:', true); + expect(log.info).toHaveBeenCalledWith( + expect.stringContaining('Requirements out of date. Scheduling package update.'), + expect.objectContaining({ coreOk: false, managerOk: false }) + ); }); - test('returns upgrade for core upgrade case', async ({ virtualEnv }) => { + test('returns upgrade when core requirements differ', async ({ virtualEnv }) => { mockSpawnOutputOnce('Would install 1 package \n + av==1.0.0\n'); mockSpawnOutputOnce('Would make no changes\n'); @@ -259,11 +258,11 @@ describe('VirtualEnvironment', () => { await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'ok' }); }); - test('rejects core upgrade with unrecognized package removal', async ({ virtualEnv }) => { + test('returns upgrade when requirements remove packages', async ({ virtualEnv }) => { mockSpawnOutputOnce(' - unknown-package==1.0.0\n + aiohttp==3.9.0\n', 0, null); mockSpawnOutputOnce('Would make no changes\n', 0, null); - await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'missing' }); + await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); }); }); From 4493fd4c1c487b4ae1d13eccb6e9f0f0f3e3945d Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 17 Jan 2026 13:36:13 -0800 Subject: [PATCH 06/10] Add test coverage for nvidia-torch case --- tests/unit/virtualEnvironment.test.ts | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unit/virtualEnvironment.test.ts b/tests/unit/virtualEnvironment.test.ts index 7d0dcefea..dbc20a426 100644 --- a/tests/unit/virtualEnvironment.test.ts +++ b/tests/unit/virtualEnvironment.test.ts @@ -264,6 +264,34 @@ describe('VirtualEnvironment', () => { await expect(virtualEnv.hasRequirements()).resolves.toMatchObject({ status: 'upgrade' }); }); + + test('returns upgrade with nvidia-torch reason when torch packages are outdated', async () => { + vi.stubGlobal('process', { + ...process, + resourcesPath: path.join(__dirname, '../resources'), + }); + + const nvidiaEnv = new VirtualEnvironment('/mock/venv', { + telemetry: mockTelemetry, + selectedDevice: 'nvidia', + pythonVersion: '3.12', + }); + + mockSpawnOutputOnce('Would make no changes\n'); + mockSpawnOutputOnce('Would make no changes\n'); + mockSpawnOutputOnce( + JSON.stringify([ + { name: 'torch', version: '2.0.0+cu121' }, + { name: 'torchaudio', version: '2.0.0+cu121' }, + { name: 'torchvision', version: '0.15.0+cu121' }, + ]) + ); + + await expect(nvidiaEnv.hasRequirements()).resolves.toMatchObject({ + status: 'upgrade', + reason: 'nvidia-torch', + }); + }); }); describe('uvEnv', () => { From cf5702f5853f63b598722ad4bdf6334ce085b7ef Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 17 Jan 2026 14:34:39 -0800 Subject: [PATCH 07/10] Update venv troubleshooting e2e expectation --- .../post-install/troubleshootingVenv.spec.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/integration/post-install/troubleshootingVenv.spec.ts b/tests/integration/post-install/troubleshootingVenv.spec.ts index 2fdbdd028..f48053a76 100644 --- a/tests/integration/post-install/troubleshootingVenv.spec.ts +++ b/tests/integration/post-install/troubleshootingVenv.spec.ts @@ -11,21 +11,20 @@ test.describe('Troubleshooting - broken venv', () => { await expect(window).toHaveScreenshot('troubleshooting-venv.png'); }); - test('Can fix venv', async ({ troubleshooting, installedApp }) => { + test('Can fix venv', async ({ troubleshooting, installedApp, window }) => { test.slow(); await troubleshooting.expectReady(); - const { resetVenvCard, installPythonPackagesCard } = troubleshooting; + const { resetVenvCard } = troubleshooting; await expect(resetVenvCard.rootEl).toBeVisible(); await resetVenvCard.button.click(); await troubleshooting.confirmRecreateVenvButton.click(); await expect(resetVenvCard.isRunningIndicator).toBeVisible(); - await expect(installPythonPackagesCard.rootEl).toBeVisible({ timeout: 60 * 1000 }); - await installPythonPackagesCard.button.click(); - await troubleshooting.confirmInstallPythonPackagesButton.click(); - await expect(installPythonPackagesCard.isRunningIndicator).toBeVisible(); + await expect(window.getByRole('heading', { name: 'Updating ComfyUI Desktop' })).toBeVisible({ + timeout: 60 * 1000, + }); // Venv fixed - server should start await installedApp.waitUntilLoaded(3 * 60 * 1000); From 58d3e8649e84db0a491c152107d5e13bcd90a99f Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 17 Jan 2026 15:28:59 -0800 Subject: [PATCH 08/10] Update test expectation --- tests/integration/post-install/troubleshootingVenv.spec.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/integration/post-install/troubleshootingVenv.spec.ts b/tests/integration/post-install/troubleshootingVenv.spec.ts index f48053a76..ab0848402 100644 --- a/tests/integration/post-install/troubleshootingVenv.spec.ts +++ b/tests/integration/post-install/troubleshootingVenv.spec.ts @@ -11,7 +11,7 @@ test.describe('Troubleshooting - broken venv', () => { await expect(window).toHaveScreenshot('troubleshooting-venv.png'); }); - test('Can fix venv', async ({ troubleshooting, installedApp, window }) => { + test('Can fix venv', async ({ troubleshooting, installedApp }) => { test.slow(); await troubleshooting.expectReady(); @@ -22,10 +22,6 @@ test.describe('Troubleshooting - broken venv', () => { await troubleshooting.confirmRecreateVenvButton.click(); await expect(resetVenvCard.isRunningIndicator).toBeVisible(); - await expect(window.getByRole('heading', { name: 'Updating ComfyUI Desktop' })).toBeVisible({ - timeout: 60 * 1000, - }); - // Venv fixed - server should start await installedApp.waitUntilLoaded(3 * 60 * 1000); }); From 7da80a93478bf76bd07226fdff06fdc89485a841 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 17 Jan 2026 15:30:24 -0800 Subject: [PATCH 09/10] Remove unneeded anys --- tests/unit/main-process/comfyServer.test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/unit/main-process/comfyServer.test.ts b/tests/unit/main-process/comfyServer.test.ts index 6f056f788..c86623e87 100644 --- a/tests/unit/main-process/comfyServer.test.ts +++ b/tests/unit/main-process/comfyServer.test.ts @@ -64,13 +64,7 @@ const test = baseTest.extend({ transports: { file: { transforms: [] } }, } as unknown as MainLogger & { default: MainLogger }); - const server = new ComfyServer( - basePath, - mockServerArgs, - mockVirtualEnvironment as any, - mockAppWindow as any, - mockTelemetry as any - ); + const server = new ComfyServer(basePath, mockServerArgs, mockVirtualEnvironment, mockAppWindow, mockTelemetry); await use(server); }, runningServer: async ({ server, mockProcess }, use) => { From 0f19ab23b855fa1a7aaaf719f55bb47809e28c05 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 17 Jan 2026 15:53:54 -0800 Subject: [PATCH 10/10] Rename upgradePackages to needsPackageUpdate Change from ValidationIssueState to boolean for clearer intent: - upgradePackages?: ValidationIssueState -> needsPackageUpdate?: boolean - The field is a flag for auto-update, not a validation issue state --- src/main-process/comfyInstallation.ts | 4 ++-- src/preload.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main-process/comfyInstallation.ts b/src/main-process/comfyInstallation.ts index e2e50a337..0da7e7745 100644 --- a/src/main-process/comfyInstallation.ts +++ b/src/main-process/comfyInstallation.ts @@ -44,7 +44,7 @@ export class ComfyInstallation { /** `true` if Manager needs toml and uv to be installed, otherwise `false`. */ get needsRequirementsUpdate() { - return this.validation.upgradePackages === 'warning'; + return this.validation.needsPackageUpdate === true; } /** @@ -187,7 +187,7 @@ export class ComfyInstallation { switch (result.status) { case 'upgrade': validation.pythonPackages = 'OK'; - validation.upgradePackages = 'warning'; + validation.needsPackageUpdate = true; break; case 'ok': validation.pythonPackages = 'OK'; diff --git a/src/preload.ts b/src/preload.ts index f795f38e8..478cd0f55 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -118,7 +118,8 @@ export interface InstallValidation { uv?: ValidationIssueState; git?: ValidationIssueState; vcRedist?: ValidationIssueState; - upgradePackages?: ValidationIssueState; + /** True if packages need updating (not an error, will auto-update) */ + needsPackageUpdate?: boolean; } const electronAPI = {