Comfy-Org / Comfy-Org/ComfyUI_frontend

LGraphCanvas constructor crashes when attaching to a native subgraph and leaves a stale canvas registered

Open
#16,035 5 comments 0 reactions 0 assignees View on GitHub
area:subgraph Potential Bug
Dominant language
TypeScript
Stars
2k
Forks
699
Avg merge
1d 7h
Merged PRs (30d)
490

Description

### Prerequisites

- [x] I am running the latest version of ComfyUI
- [ ] I have custom nodes enabled

### What happened?

Creating an additional `LGraphCanvas` for an active native subgraph throws:

```text
Cannot read properties of undefined (reading 'dispatchEvent')
```

This is reproducible with ComfyUI started using:

```text
--disable-all-custom-nodes
```

No custom-node code is involved in the reproduction.

The `LGraphCanvas` constructor registers the new canvas with the supplied subgraph before its HTML canvas has been initialized. The native subgraph attachment updates `LGraphCanvas.subgraph`, which dispatches an event through `this.canvas`. At that point, `this.canvas` is still `undefined`.

The failed constructor also leaves the partially initialized `LGraphCanvas` registered in `graph.list_of_graphcanvas`.

Observed state:

```json
{
"ok": false,
"graphName": "New Subgraph",
"sourceSubgraph": "New Subgraph",
"error": "Cannot read properties of undefined (reading 'dispatchEvent')",
"registeredBefore": 1,
"registeredAfter": 2,
"canvases": [
{
"index": 0,
"hasCanvas": true,
"connected": true,
"graphMatches": true
},
{
"index": 1,
"hasCanvas": false,
"connected": false,
"graphMatches": true
}
]
}
```

The second entry is registered to the active subgraph even though construction failed and its `.canvas` property was never initialized.

This stale entry can cause later native graph operations to fail when they dispatch actions to every registered canvas.

The complete stack trace is:

```text
TypeError: Cannot read properties of undefined (reading 'dispatchEvent')
at LGraphCanvas.dispatch
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:261707)
at set subgraph
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:261541)
at Subgraph.attachCanvas
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:407263)
at new LGraphCanvas
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:269349)
at :18:7
at :60:3
```

Environment:

- ComfyUI: `0.34.0`
- Bundled ComfyUI Frontend: `1.49.6`
- Startup option: `--disable-all-custom-nodes`
- OS: Windows
- Browser: Brave/Chromium
- GPU: NVIDIA GeForce RTX 3090

Expected behavior:

- `new LGraphCanvas(canvas, graph)` should work for native root graphs and native subgraphs.
- The supplied HTML canvas should be initialized before attach-time code dispatches events through it.
- If construction fails, the partially initialized instance must not remain in `graph.list_of_graphcanvas`.
- Later native graph operations must not encounter a registered canvas whose `.canvas` is undefined.

Relevant source:

- [`LGraphCanvas` constructor](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/src/lib/litegraph/src/LGraphCanvas.ts#L968-L980)
- [`LGraphCanvas` event dispatch](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/src/lib/litegraph/src/LGraphCanvas.ts#L339-L357)
- [`LGraph.attachCanvas`](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/src/lib/litegraph/src/LGraph.ts#L3306-L3338)

### Steps to Reproduce

1. Start the latest ComfyUI release with all custom nodes disabled:

```text
python main.py --disable-all-custom-nodes
```

2. Create a new workflow.
3. Add two arbitrary native nodes.
4. Select both nodes.
5. Use the native **Convert Selection to Subgraph** action.
6. Enter the resulting native subgraph.
7. Open the browser developer console.
8. Execute:

```js
(() => {
const sourceCanvas =
document.querySelector("canvas.lgraphcanvas")?.data;

const graph =
sourceCanvas?.getCurrentGraph?.() ??
sourceCanvas?.graph;

const before =
graph?.list_of_graphcanvas?.length ?? null;

const element = document.createElement("canvas");
element.width = 512;
element.height = 512;

try {
const additionalCanvas =
new sourceCanvas.constructor(element, graph);

return {
ok: true,
graphName: graph?.name ?? graph?.title ?? null,
sourceSubgraph:
sourceCanvas?.subgraph?.name ??
sourceCanvas?.subgraph?.title ??
null,
additionalSubgraph:
additionalCanvas?.subgraph?.name ??
additionalCanvas?.subgraph?.title ??
null,
hasCanvas: !!additionalCanvas?.canvas,
connected:
additionalCanvas?.canvas?.isConnected ??
false,
registeredBefore: before,
registeredAfter:
graph?.list_of_graphcanvas?.length ?? null
};
} catch (error) {
return {
ok: false,
graphName: graph?.name ?? graph?.title ?? null,
sourceSubgraph:
sourceCanvas?.subgraph?.name ??
sourceCanvas?.subgraph?.title ??
null,
error: error?.message ?? String(error),
stack: error?.stack ?? null,
registeredBefore: before,
registeredAfter:
graph?.list_of_graphcanvas?.length ?? null,
canvases:
graph?.list_of_graphcanvas?.map(
(canvas, index) => ({
index,
hasCanvas: !!canvas.canvas,
connected:
canvas.canvas?.isConnected ??
false,
graphMatches: canvas.graph === graph
})
)
};
}
})()
```

9. Observe the `dispatchEvent` exception.
10. Observe that `registeredAfter` is one greater than `registeredBefore`.
11. Observe that the newly registered entry has:

```json
{
"hasCanvas": false,
"connected": false,
"graphMatches": true
}
```

### Workflow

N/A — the issue is reproducible with any two native nodes converted into a native subgraph. It does not depend on a particular workflow, node type, or custom node.

### How is this affecting you?

Feature doesn't work as expected

### ComfyUI Frontend Version

1.49.6

### Browser

Chrome/Chromium

### Console Errors

```javascript
TypeError: Cannot read properties of undefined (reading 'dispatchEvent')
at LGraphCanvas.dispatch
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:261707)
at set subgraph
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:261541)
at Subgraph.attachCanvas
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:407263)
at new LGraphCanvas
(http://127.0.0.1:8188/assets/settingStore-CwkLtSKP.js:2:269349)
at :18:7
at :60:3
```

### Logs

```shell
N/A — this is a browser-side frontend exception. No corresponding Python traceback or ComfyUI server error was produced.
```

### Additional Context

We initially encountered this issue while implementing a graph screenshot exporter that renders the currently active graph using a separate offscreen `LGraphCanvas`.

The exporter intentionally creates an additional canvas instead of resizing, repositioning, or otherwise modifying ComfyUI's visible canvas. This works for root graphs but exposed the constructor failure when the active graph is a native subgraph.

After encountering the problem through the extension, we reduced it to the standalone console reproduction included above and reproduced it with ComfyUI started using:

```text
--disable-all-custom-nodes
```

The reduced reproduction uses only:

- a native ComfyUI subgraph;
- an HTML canvas created with `document.createElement("canvas")`;
- the existing native `LGraphCanvas` constructor;
- the constructor's `(canvas, graph)` parameters.

The custom screenshot extension is therefore the original real-world use case, but it is not required to reproduce the underlying frontend failure.

The failed constructor increases `graph.list_of_graphcanvas.length` from `1` to `2`. The newly retained entry references the active graph but has no initialized `.canvas`, demonstrating that the constructor failure also leaves graph state inconsistent.

A possible caller-side workaround is to construct the offscreen `LGraphCanvas` without supplying the graph and attach the graph only after canvas initialization. However, the constructor accepting both `canvas` and `graph` should either complete safely or roll back its graph registration when initialization fails.

Contributor guide

Open the contributing guide

Research direction

Start with the LGraphCanvas constructor and event dispatch code in src/lib/litegraph/src/LGraphCanvas.ts, then read LGraph.attachCanvas in src/lib/litegraph/src/LGraph.ts. Reproduce the supplied console sequence against a native subgraph and inspect graph.list_of_graphcanvas before and after construction. Done means subgraph attachment does not dispatch through an uninitialized canvas, and a failed construction leaves no stale registered canvas.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.