[backport cloud/1.40] fix: fix error overlay and TabErrors filtering for nested subgraphs (#9133)

Backport of #9129 to `cloud/1.40`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9133-backport-cloud-1-40-fix-fix-error-overlay-and-TabErrors-filtering-for-nested-subgraphs-3106d73d3650818cb0d9d8e96e60ae37)
by [Unito](https://www.unito.io)

Co-authored-by: jaeone94 <89377375+jaeone94@users.noreply.github.com>
This commit is contained in:
Comfy Org PR Bot
2026-02-23 21:14:00 +09:00
committed by GitHub
parent d4f6a9af0e
commit b6ca126eff
6 changed files with 80 additions and 16 deletions

View File

@@ -106,7 +106,7 @@ const hasContainerInternalError = computed(() => {
if (allErrorExecutionIds.value.length === 0) return false
return selectedNodes.value.some((node) => {
if (!(node instanceof SubgraphNode || isGroupNode(node))) return false
return executionErrorStore.hasInternalErrorForNode(node.id)
return executionErrorStore.isContainerWithInternalError(node)
})
})

View File

@@ -13,6 +13,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import {
getNodeByExecutionId,
getExecutionIdByNode,
getRootParentNode
} from '@/utils/graphTraversalUtil'
import { resolveNodeDisplayName } from '@/utils/nodeTitleUtil'
@@ -20,6 +21,7 @@ import { isLGraphNode } from '@/utils/litegraphUtil'
import { isGroupNode } from '@/utils/executableGroupNodeDto'
import { st } from '@/i18n'
import type { ErrorCardData, ErrorGroup, ErrorItem } from './types'
import type { NodeExecutionId } from '@/types/nodeIdentification'
import { isNodeExecutionId } from '@/types/nodeIdentification'
const PROMPT_CARD_ID = '__prompt__'
@@ -200,26 +202,30 @@ export function useErrorGroups(
const selectedNodeInfo = computed(() => {
const items = canvasStore.selectedItems
const nodeIds = new Set<string>()
const containerIds = new Set<string>()
const containerExecutionIds = new Set<NodeExecutionId>()
for (const item of items) {
if (!isLGraphNode(item)) continue
nodeIds.add(String(item.id))
if (item instanceof SubgraphNode || isGroupNode(item)) {
containerIds.add(String(item.id))
if (
(item instanceof SubgraphNode || isGroupNode(item)) &&
app.rootGraph
) {
const execId = getExecutionIdByNode(app.rootGraph, item)
if (execId) containerExecutionIds.add(execId)
}
}
return {
nodeIds: nodeIds.size > 0 ? nodeIds : null,
containerIds
containerExecutionIds
}
})
const isSingleNodeSelected = computed(
() =>
selectedNodeInfo.value.nodeIds?.size === 1 &&
selectedNodeInfo.value.containerIds.size === 0
selectedNodeInfo.value.containerExecutionIds.size === 0
)
const errorNodeCache = computed(() => {
@@ -238,8 +244,9 @@ export function useErrorGroups(
const graphNode = errorNodeCache.value.get(executionNodeId)
if (graphNode && nodeIds.has(String(graphNode.id))) return true
for (const containerId of selectedNodeInfo.value.containerIds) {
if (executionNodeId.startsWith(`${containerId}:`)) return true
for (const containerExecId of selectedNodeInfo.value
.containerExecutionIds) {
if (executionNodeId.startsWith(`${containerExecId}:`)) return true
}
return false

View File

@@ -121,7 +121,7 @@ const hasContainerInternalError = computed(() => {
targetNode.value instanceof SubgraphNode || isGroupNode(targetNode.value)
if (!isContainer) return false
return executionErrorStore.hasInternalErrorForNode(targetNode.value.id)
return executionErrorStore.isContainerWithInternalError(targetNode.value)
})
const nodeHasError = computed(() => {

View File

@@ -342,7 +342,9 @@ const hasAnyError = computed((): boolean => {
hasExecutionError.value ||
nodeData.hasErrors ||
error ||
(executionErrorStore.lastNodeErrors?.[nodeData.id]?.errors.length ?? 0) > 0
executionErrorStore.getNodeErrors(nodeLocatorId.value) ||
(lgraphNode.value &&
executionErrorStore.isContainerWithInternalError(lgraphNode.value))
)
})

View File

@@ -10,11 +10,13 @@ import type {
PromptError
} from '@/schemas/apiSchema'
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
import type { NodeLocatorId } from '@/types/nodeIdentification'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { NodeExecutionId, NodeLocatorId } from '@/types/nodeIdentification'
import {
executionIdToNodeLocatorId,
forEachNode,
getNodeByExecutionId
getNodeByExecutionId,
getExecutionIdByNode
} from '@/utils/graphTraversalUtil'
/**
@@ -186,9 +188,32 @@ export const useExecutionErrorStore = defineStore('executionError', () => {
return nodeError.errors.some((e) => e.extra_info?.input_name === slotName)
}
function hasInternalErrorForNode(nodeId: string | number): boolean {
const prefix = `${nodeId}:`
return allErrorExecutionIds.value.some((id) => id.startsWith(prefix))
/**
* Set of all execution ID prefixes derived from active error nodes,
* including the error nodes themselves.
*
* Example: error at "65:70:63" → Set { "65", "65:70", "65:70:63" }
*/
const errorAncestorExecutionIds = computed<Set<NodeExecutionId>>(() => {
const ids = new Set<NodeExecutionId>()
for (const executionId of allErrorExecutionIds.value) {
const parts = executionId.split(':')
// Add every prefix including the full ID (error leaf node itself)
for (let i = 1; i <= parts.length; i++) {
ids.add(parts.slice(0, i).join(':'))
}
}
return ids
})
/** True if the node has errors inside it at any nesting depth. */
function isContainerWithInternalError(node: LGraphNode): boolean {
if (!app.rootGraph) return false
const execId = getExecutionIdByNode(app.rootGraph, node)
if (!execId) return false
return errorAncestorExecutionIds.value.has(execId)
}
/**
@@ -275,6 +300,7 @@ export const useExecutionErrorStore = defineStore('executionError', () => {
// Lookup helpers
getNodeErrors,
slotHasError,
hasInternalErrorForNode
errorAncestorExecutionIds,
isContainerWithInternalError
}
})

View File

@@ -331,6 +331,35 @@ export function getNodeByExecutionId(
return targetGraph.getNodeById(localNodeId) || null
}
/**
* Returns the execution ID for a node relative to the root graph.
*
* Root-level nodes return their ID directly (e.g. "42").
* Nodes inside subgraphs return a colon-separated chain (e.g. "65:70:63").
*
* @param rootGraph - The root graph to resolve from
* @param node - The node whose execution ID to compute
* @returns The execution ID string, or null if the node has no graph
*/
export function getExecutionIdByNode(
rootGraph: LGraph,
node: LGraphNode
): NodeExecutionId | null {
if (!node.graph) return null
if (node.graph === rootGraph || node.graph.isRootGraph) {
return String(node.id)
}
const parentPath = findPartialExecutionPathToGraph(
node.graph as LGraph,
rootGraph
)
if (parentPath === undefined) return null
return `${parentPath}:${node.id}`
}
/**
* Get a node by its locator ID from anywhere in the graph hierarchy.
* Locator IDs use UUID format like "uuid:nodeId" for subgraph nodes.