From 2ec954459cbc4e8401717297b654bc32b3a6db28 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Mon, 2 Feb 2026 01:28:17 -0800 Subject: [PATCH] fix: show Missing Nodes dialog for missing node prompt errors (#8511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary When a workflow is queued with a missing node type, show the Missing Nodes dialog instead of a generic error toast. This gives users actionable options like "Open Manager" and "Install All". ## Changes - Detect `missing_node_type` error from backend in `queuePrompt()` catch block - Construct `MissingNodeType` object with class type and contextual hint - Reuse existing `showMissingNodesError()` to trigger the dialog ## Dependencies ⚠️ **Requires backend PR:** https://github.com/Comfy-Org/ComfyUI/pull/12177 The backend PR changes the error type from `invalid_prompt` to `missing_node_type` and adds `extra_info` with node details. ## Related - Fixes COM-12528 - Addresses ~49 GitHub issues with confusing "missing class_type" errors ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8511-fix-show-Missing-Nodes-dialog-for-missing-node-prompt-errors-2f96d73d3650812b95f0f08e51abaabb) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp --- src/scripts/app.ts | 21 ++++- .../manager/types/missingNodeErrorTypes.ts | 9 ++ .../utils/missingNodeErrorUtil.test.ts | 90 +++++++++++++++++++ .../manager/utils/missingNodeErrorUtil.ts | 37 ++++++++ 4 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 src/workbench/extensions/manager/types/missingNodeErrorTypes.ts create mode 100644 src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts create mode 100644 src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 067b6177d..7c1749982 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -70,6 +70,8 @@ import type { ComfyExtension, MissingNodeType } from '@/types/comfy' import { type ExtensionManager } from '@/types/extensionTypes' import type { NodeExecutionId } from '@/types/nodeIdentification' import { graphToPrompt } from '@/utils/executionUtil' +import type { MissingNodeTypeExtraInfo } from '@/workbench/extensions/manager/types/missingNodeErrorTypes' +import { createMissingNodeTypeFromError } from '@/workbench/extensions/manager/utils/missingNodeErrorUtil' import { anyItemOverlapsRect } from '@/utils/mathUtil' import { collectAllNodes, forEachNode } from '@/utils/graphTraversalUtil' import { @@ -1394,10 +1396,21 @@ export class ComfyApp { } catch (error) {} } } catch (error: unknown) { - useDialogService().showErrorDialog(error, { - title: t('errorDialog.promptExecutionError'), - reportType: 'promptExecutionError' - }) + if ( + error instanceof PromptExecutionError && + typeof error.response.error === 'object' && + error.response.error?.type === 'missing_node_type' + ) { + const extraInfo = (error.response.error.extra_info ?? + {}) as MissingNodeTypeExtraInfo + const missingNodeType = createMissingNodeTypeFromError(extraInfo) + this.showMissingNodesError([missingNodeType]) + } else { + useDialogService().showErrorDialog(error, { + title: t('errorDialog.promptExecutionError'), + reportType: 'promptExecutionError' + }) + } console.error(error) if (error instanceof PromptExecutionError) { diff --git a/src/workbench/extensions/manager/types/missingNodeErrorTypes.ts b/src/workbench/extensions/manager/types/missingNodeErrorTypes.ts new file mode 100644 index 000000000..8af9b21d2 --- /dev/null +++ b/src/workbench/extensions/manager/types/missingNodeErrorTypes.ts @@ -0,0 +1,9 @@ +/** + * Extra info returned by the backend for missing_node_type errors + * from the /prompt endpoint validation. + */ +export interface MissingNodeTypeExtraInfo { + class_type?: string | null + node_title?: string | null + node_id?: string +} diff --git a/src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts new file mode 100644 index 000000000..0200fa20e --- /dev/null +++ b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it } from 'vitest' + +import { + buildMissingNodeHint, + createMissingNodeTypeFromError +} from './missingNodeErrorUtil' + +describe('buildMissingNodeHint', () => { + it('returns hint with title and node ID when both available', () => { + expect(buildMissingNodeHint('My Node', 'MyNodeClass', '42')).toBe( + '"My Node" (Node ID #42)' + ) + }) + + it('returns hint with title only when no node ID', () => { + expect(buildMissingNodeHint('My Node', 'MyNodeClass', undefined)).toBe( + '"My Node"' + ) + }) + + it('returns hint with node ID only when title matches class type', () => { + expect(buildMissingNodeHint('MyNodeClass', 'MyNodeClass', '42')).toBe( + 'Node ID #42' + ) + }) + + it('returns undefined when title matches class type and no node ID', () => { + expect( + buildMissingNodeHint('MyNodeClass', 'MyNodeClass', undefined) + ).toBeUndefined() + }) + + it('returns undefined when title is null and no node ID', () => { + expect(buildMissingNodeHint(null, 'MyNodeClass', undefined)).toBeUndefined() + }) + + it('returns node ID hint when title is null but node ID exists', () => { + expect(buildMissingNodeHint(null, 'MyNodeClass', '42')).toBe('Node ID #42') + }) +}) + +describe('createMissingNodeTypeFromError', () => { + it('returns string type when no hint is generated', () => { + const result = createMissingNodeTypeFromError({ + class_type: 'MyNodeClass', + node_title: 'MyNodeClass' + }) + expect(result).toBe('MyNodeClass') + }) + + it('returns object with hint when title differs from class type', () => { + const result = createMissingNodeTypeFromError({ + class_type: 'MyNodeClass', + node_title: 'My Custom Title', + node_id: '42' + }) + expect(result).toEqual({ + type: 'MyNodeClass', + hint: '"My Custom Title" (Node ID #42)' + }) + }) + + it('handles null class_type by defaulting to Unknown', () => { + const result = createMissingNodeTypeFromError({ + class_type: null, + node_title: 'Some Title', + node_id: '42' + }) + expect(result).toEqual({ + type: 'Unknown', + hint: '"Some Title" (Node ID #42)' + }) + }) + + it('handles empty extra_info', () => { + const result = createMissingNodeTypeFromError({}) + expect(result).toBe('Unknown') + }) + + it('returns object with node ID hint when only node_id is available', () => { + const result = createMissingNodeTypeFromError({ + class_type: 'MyNodeClass', + node_id: '123' + }) + expect(result).toEqual({ + type: 'MyNodeClass', + hint: 'Node ID #123' + }) + }) +}) diff --git a/src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts new file mode 100644 index 000000000..34f84f88d --- /dev/null +++ b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts @@ -0,0 +1,37 @@ +import type { MissingNodeType } from '@/types/comfy' + +import type { MissingNodeTypeExtraInfo } from '../types/missingNodeErrorTypes' + +/** + * Builds a hint string from missing node metadata. + * Provides context about which node is missing (title, ID) when available. + */ +export function buildMissingNodeHint( + nodeTitle: string | null | undefined, + classType: string, + nodeId: string | undefined +): string | undefined { + const hasTitle = nodeTitle && nodeTitle !== classType + if (hasTitle && nodeId) { + return `"${nodeTitle}" (Node ID #${nodeId})` + } else if (hasTitle) { + return `"${nodeTitle}"` + } else if (nodeId) { + return `Node ID #${nodeId}` + } + return undefined +} + +/** + * Creates a MissingNodeType from backend error extra_info. + * Used when the /prompt endpoint returns a missing_node_type error. + */ +export function createMissingNodeTypeFromError( + extraInfo: MissingNodeTypeExtraInfo +): MissingNodeType { + const classType = extraInfo.class_type ?? 'Unknown' + const nodeTitle = extraInfo.node_title ?? classType + const hint = buildMissingNodeHint(nodeTitle, classType, extraInfo.node_id) + + return hint ? { type: classType, hint } : classType +}