From 25cad3ec110f7a3c3c1b56f80d223d83cd5c4f24 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Tue, 8 Sep 2026 15:48:03 -0700 Subject: [PATCH] feat(install): say why ARM64 Linux has no local runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filtering every bundle out left getFieldOptions returning [], which reaches the user as "No options available" and reaches telemetry as express.fallback{precondition_failed} — the same as an empty or unreachable catalog. The one state we know exactly was indistinguishable from the one we do not. Give the reason a home next to the filter that creates it and let validateHardware report it, so ARM64 Linux takes the explain-and-block path the wizard already gives an Intel Mac: a message naming the platform, and express falling back with unsupported_hardware. Cloud and remote workspaces are untouched — the wizard only blocks the standalone source card. --- src/main/lib/gpu.test.ts | 34 ++++++++++++++++++++ src/main/lib/gpu.ts | 13 ++++++-- src/main/sources/standalone/envPaths.test.ts | 26 +++++++++++++++ src/main/sources/standalone/envPaths.ts | 19 +++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/main/lib/gpu.test.ts b/src/main/lib/gpu.test.ts index 543f9a66b..733905896 100644 --- a/src/main/lib/gpu.test.ts +++ b/src/main/lib/gpu.test.ts @@ -11,6 +11,7 @@ import { parseRocmSmiDriverVersion, parseWmiDriverVersions, checkLinuxAmdKfdAccess, + validateHardware, type SystemGpuEntry } from './gpu' @@ -281,3 +282,36 @@ describe('checkLinuxAmdKfdAccess', () => { expect(await checkLinuxAmdKfdAccess(node)).toBeNull() }) }) + +describe('validateHardware architecture gate', () => { + const realPlatform = process.platform + const realArch = process.arch + const setHost = (platform: string, arch: string): void => { + Object.defineProperty(process, 'platform', { value: platform }) + Object.defineProperty(process, 'arch', { value: arch }) + } + + afterEach(() => { + setHost(realPlatform, realArch) + }) + + // Without this the wizard filters every bundle out and shows a bare "No + // options available", which the user cannot tell from an R2 outage. + it('blocks the local install on an architecture with no published bundle', async () => { + setHost('linux', 'arm64') + const result = await validateHardware() + + expect(result.supported).toBe(false) + expect(result.error).toMatch(/ARM64 Linux/) + // The other half of the message: this machine is still useful. + expect(result.error).toMatch(/cloud or remote/) + }) + + it('does not gate an x64 Linux host on architecture', async () => { + setHost('linux', 'x64') + const result = await validateHardware() + + // May still warn about the AMD compute node; it must not be blocked here. + expect(result.error).toBeUndefined() + }) +}) diff --git a/src/main/lib/gpu.ts b/src/main/lib/gpu.ts index e562e3421..951497866 100644 --- a/src/main/lib/gpu.ts +++ b/src/main/lib/gpu.ts @@ -1,6 +1,7 @@ import { execFile } from 'child_process' import fs from 'fs' import type { HardwareValidation, NvidiaDriverCheck } from '../../types/ipc' +import { unsupportedHostReason } from '../sources/standalone/envPaths' type GpuId = 'nvidia' | 'amd' | 'intel' | 'mps' @@ -559,9 +560,17 @@ export async function checkLinuxAmdKfdAccess(kfdPath = KFD_PATH): Promise { + // Ask before probing the GPU: no bundle exists for this architecture, so + // whatever the GPU turns out to be, the local install has nothing to offer. + // Reporting it here gets the wizard's existing explain-and-block treatment + // instead of an empty release list the user cannot tell from an outage. + const unsupportedHost = unsupportedHostReason() + if (unsupportedHost) return { supported: false, error: unsupportedHost } + if (process.platform === 'darwin') { const gpu = await detectMacGPU() if (!gpu) { diff --git a/src/main/sources/standalone/envPaths.test.ts b/src/main/sources/standalone/envPaths.test.ts index ea0e6b862..8c65e6e8d 100644 --- a/src/main/sources/standalone/envPaths.test.ts +++ b/src/main/sources/standalone/envPaths.test.ts @@ -16,6 +16,7 @@ import { recommendVariant, stripPlatform, variantAccel, + unsupportedHostReason, variantMatchesHost, variantMatchesHostArch } from './envPaths' @@ -210,6 +211,31 @@ describe('architecture-specific vendor ids', () => { }) }) + describe('unsupportedHostReason', () => { + // The filter and the reason have to agree: a host the filter empties out + // must have something to say, or the wizard shows a bare "no options". + const CATALOG = ['win-nvidia', 'win-nvidia-arm64', 'mac-mps', 'linux-nvidia'] + + it('explains the ARM64 Linux gap the architecture filter creates', () => { + setHost('linux', 'arm64') + expect(CATALOG.some(variantMatchesHost)).toBe(false) + expect(unsupportedHostReason()).toMatch(/ARM64 Linux/) + }) + + it('stays silent on hosts the catalog serves', () => { + for (const [platform, arch] of [ + ['win32', 'x64'], + ['win32', 'arm64'], + ['darwin', 'arm64'], + ['linux', 'x64'] + ] as const) { + setHost(platform, arch) + expect(CATALOG.some(variantMatchesHost), `${platform}/${arch}`).toBe(true) + expect(unsupportedHostReason(), `${platform}/${arch}`).toBeNull() + } + }) + }) + describe('variantMatchesHost', () => { it('accepts the beta- prefix only directly in front of the host platform', () => { setHost('win32', 'arm64') diff --git a/src/main/sources/standalone/envPaths.ts b/src/main/sources/standalone/envPaths.ts index 69f27dfc8..5d3201bcd 100644 --- a/src/main/sources/standalone/envPaths.ts +++ b/src/main/sources/standalone/envPaths.ts @@ -79,6 +79,25 @@ export function variantMatchesHostArch(variantId: string): boolean { return false } +/** + * Why the running host has no standalone bundle at all, or null when it has + * some. `variantMatchesHostArch` filters an ARM64 Linux app down to nothing, + * and an empty option list reads exactly like an R2 outage — so the reason + * lives here, next to the filter that creates it, and `validateHardware` + * turns it into the same block-and-explain the wizard already gives an Intel + * Mac. Delete this carve-out when `linux-*-arm64` bundles ship; the filter + * above accepts them with no further change. + */ +export function unsupportedHostReason(): string | null { + if (process.platform === 'linux' && process.arch === 'arm64') { + return ( + 'ComfyUI does not publish a local runtime for ARM64 Linux yet. ' + + 'You can still connect this machine to cloud or remote workspaces.' + ) + } + return null +} + /** True when a vendor id targets the running platform, with or without the * `beta-` prefix (`win-nvidia` and `beta-win-nvidia-arm64` on Windows). */ export function variantMatchesHostPlatform(variantId: string): boolean {