Skip to content

Commit ad4d328

Browse files
fix: give imported workflows their own run-error bucket
loadApiJson() and importA1111() populate the root graph in place and never call configure(), so it keeps the zero id that LGraph.clear() leaves behind. Every such import shared one bucket, and the first reload swapped the zero id for a generated one, losing the parked errors again. afterLoadNewGraph() now mints an id for a zero-id root graph before selecting the bucket, and writes it back into the state this load persists so the reload lands on the same key. Addresses #15361 (comment)
1 parent 9fd8f04 commit ad4d328

2 files changed

Lines changed: 81 additions & 3 deletions

File tree

src/platform/workflow/core/services/workflowService.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,31 @@ import {
3737
generateUUID
3838
} from '@/utils/formatUtil'
3939
import type { AppMode } from '@/utils/appMode'
40+
import type { UUID } from '@/utils/uuid'
41+
import { createUuidv4, zeroUuid } from '@/utils/uuid'
4042

4143
function linearModeToAppMode(linearMode: unknown): AppMode | null {
4244
if (typeof linearMode !== 'boolean') return null
4345
return linearMode ? 'app' : 'graph'
4446
}
4547

48+
/**
49+
* Returns the root graph id to scope run errors by, minting one when the graph
50+
* carries the zero id. `loadApiJson` and `importA1111` populate the root graph
51+
* without `configure()`, so every such import would otherwise share the zero
52+
* id's bucket and lose it once a reload generated a real id. The id is written
53+
* back into `workflowData` so the state this load persists reloads under the
54+
* same key.
55+
*/
56+
function adoptRootGraphId(workflowData: ComfyWorkflowJSON): UUID | null {
57+
if (!app.isGraphReady) return null
58+
59+
const rootGraph = app.rootGraph
60+
if (rootGraph.id === zeroUuid) rootGraph.id = createUuidv4()
61+
workflowData.id = rootGraph.id
62+
return rootGraph.id
63+
}
64+
4665
export const useWorkflowService = () => {
4766
const settingStore = useSettingStore()
4867
const workflowStore = useWorkflowStore()
@@ -452,9 +471,7 @@ export const useWorkflowService = () => {
452471
const { isAppMode } = useAppMode()
453472
const wasAppMode = isAppMode.value
454473

455-
useExecutionErrorStore().setActiveGraph(
456-
app.isGraphReady ? app.rootGraph.id : null
457-
)
474+
useExecutionErrorStore().setActiveGraph(adoptRootGraphId(workflowData))
458475

459476
// Determine the initial app mode for fresh loads from serialized state.
460477
// null means linearMode was never explicitly set (not builder-saved).

src/scripts/app.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
} from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers'
5151
import { useWidgetValueStore } from '@/stores/widgetValueStore'
5252
import { extractFilesFromDragEvent } from '@/utils/eventUtils'
53+
import { zeroUuid } from '@/utils/uuid'
5354
import type { importA1111 } from './pnginfo'
5455

5556
type WorkflowService = ReturnType<typeof useWorkflowService>
@@ -1182,6 +1183,20 @@ describe('ComfyApp', () => {
11821183
)
11831184
}
11841185

1186+
/** Mirrors `loadApiJson`/`importA1111`: the root graph is populated in
1187+
* place and never configured, so it still carries the zero id. */
1188+
async function importWorkflow(
1189+
workflowService: WorkflowService,
1190+
graph: LGraph,
1191+
workflow: ComfyWorkflow
1192+
): Promise<ComfyWorkflowJSON> {
1193+
workflowService.beforeLoadNewGraph()
1194+
app.clean()
1195+
const workflowData = { ...createWorkflowGraphData(), id: graph.id }
1196+
await workflowService.afterLoadNewGraph(workflow, workflowData)
1197+
return workflowData
1198+
}
1199+
11851200
it('restores the failed run state when returning to a workflow tab', async () => {
11861201
const workflowService = await useRealWorkflowService()
11871202
const graph = new LGraph()
@@ -1210,6 +1225,52 @@ describe('ComfyApp', () => {
12101225
expect(executionErrorStore.lastNodeErrors).toEqual(failedKSamplerErrors)
12111226
expect(executionErrorStore.totalErrorCount).toBe(1)
12121227
})
1228+
1229+
it('gives each imported workflow its own restorable run errors', async () => {
1230+
const workflowService = await useRealWorkflowService()
1231+
const graph = new LGraph()
1232+
Reflect.set(app, 'rootGraphInternal', graph)
1233+
Reflect.set(singletonApp, 'rootGraphInternal', graph)
1234+
1235+
const importedA = markLoaded(
1236+
new ComfyWorkflow({
1237+
path: 'workflows/api-a.json',
1238+
modified: 0,
1239+
size: 0
1240+
})
1241+
)
1242+
const importedB = markLoaded(
1243+
new ComfyWorkflow({
1244+
path: 'workflows/api-b.json',
1245+
modified: 0,
1246+
size: 0
1247+
})
1248+
)
1249+
1250+
const firstImport = await importWorkflow(
1251+
workflowService,
1252+
graph,
1253+
importedA
1254+
)
1255+
expect(firstImport.id).not.toBe(zeroUuid)
1256+
expect(firstImport.id).toBe(graph.id)
1257+
1258+
const executionErrorStore = useExecutionErrorStore()
1259+
executionErrorStore.recordNodeErrors(failedKSamplerErrors)
1260+
1261+
const secondImport = await importWorkflow(
1262+
workflowService,
1263+
graph,
1264+
importedB
1265+
)
1266+
expect(secondImport.id).not.toBe(firstImport.id)
1267+
expect(executionErrorStore.lastNodeErrors).toBeNull()
1268+
1269+
// Reopening the first import replays the state it persisted.
1270+
await switchToWorkflow(workflowService, graph, importedA, firstImport.id!)
1271+
1272+
expect(executionErrorStore.lastNodeErrors).toEqual(failedKSamplerErrors)
1273+
})
12131274
})
12141275

12151276
describe('refreshComboInNodes', () => {

0 commit comments

Comments
 (0)