fix: show Missing Nodes dialog for missing node prompt errors (#8511)

## 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 <amp@ampcode.com>
This commit is contained in:
Christian Byrne
2026-02-02 01:28:17 -08:00
committed by GitHub
parent 4e9f581625
commit 2ec954459c
4 changed files with 153 additions and 4 deletions

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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'
})
})
})

View File

@@ -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
}