[backport core/1.40] fix: node replacement fails after execution and modal sync (#9568)

Backport of #9269 to `core/1.40`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9568-backport-core-1-40-fix-node-replacement-fails-after-execution-and-modal-sync-31d6d73d365081dc8affc0e1591df4cb)
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-03-08 11:25:17 +09:00
committed by GitHub
parent 114c2ef182
commit 9f2de249f4
2 changed files with 25 additions and 4 deletions

View File

@@ -233,6 +233,7 @@ import { isCloud } from '@/platform/distribution/types'
import type { NodeReplacement } from '@/platform/nodeReplacement/types'
import { useNodeReplacement } from '@/platform/nodeReplacement/useNodeReplacement'
import { useDialogStore } from '@/stores/dialogStore'
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
import type { MissingNodeType } from '@/types/comfy'
import { cn } from '@/utils/tailwindUtil'
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
@@ -244,6 +245,7 @@ const { missingNodeTypes } = defineProps<{
const { missingCoreNodes } = useMissingNodes()
const { replaceNodesInPlace } = useNodeReplacement()
const dialogStore = useDialogStore()
const executionErrorStore = useExecutionErrorStore()
interface ProcessedNode {
label: string
@@ -338,6 +340,14 @@ function handleReplaceSelected() {
replacedTypes.value = nextReplaced
selectedTypes.value = nextSelected
// replaceNodesInPlace() handles canvas rendering via onNodeAdded(),
// but the modal only updates its own local UI state above.
// Without this call the Errors Tab would still list the replaced nodes
// as missing because executionErrorStore is not aware of the replacement.
if (result.length > 0) {
executionErrorStore.removeMissingNodesByType(result)
}
// Auto-close when all replaceable nodes replaced and no non-replaceable remain
const allReplaced = replaceableNodes.value.every((n) =>
nextReplaced.has(n.label)

View File

@@ -226,11 +226,22 @@ export function useNodeReplacement() {
useWorkflowStore().activeWorkflow?.changeTracker ?? null
changeTracker?.beforeChange()
// Target types come from node_replacements fetched at workflow load time
// and the missing nodes detected at that point — not from the current
// registered_node_types. This ensures replacement still works even if
// the user has since installed the missing node pack.
const targetTypes = new Set(
selectedTypes.map((t) => (typeof t === 'string' ? t : t.type))
)
try {
const placeholders = collectAllNodes(
graph,
(n) => !!n.has_errors && !!n.last_serialization
)
const placeholders = collectAllNodes(graph, (n) => {
if (!n.last_serialization) return false
// Prefer the original serialized type; fall back to the live type
// for nodes whose serialization predates the type field.
const originalType = n.last_serialization.type ?? n.type
return !!originalType && targetTypes.has(originalType)
})
for (const node of placeholders) {
const match = findMatchingType(node, selectedTypes)