Skip to content

Conversation

@AustinMroz
Copy link
Collaborator

@AustinMroz AustinMroz commented Feb 9, 2026

#8187 made removal of subgraphs cleanup the subgraph definition for the removed graph and call onRemove handlers. However, it missed some edge cases and broke subgraph conversion of selections containing subgraphs which this PR tries to address.

  • Deeply nested subgraphs are now also cleaned
  • Adding a subgraphNode to the graph also ensures nested subgraphs are added to subgraph definitions

Reminder: under this change, nodes can continue to exist after their onRemoved handler has been called

  • It may be better to instead perform the "garbage collection" of subgraphs outside of the graph removal step to better handle edge cases like subgraph conversion where a subgraph may continue to persist after a parent subgraphNode has been removed from a graph.

┆Issue is synchronized with this Notion page by Unito

Deeply nested subgraphs are also cleaned

Adding a subgraphNode to the graph also ensures nested subgraphs are
added to subgraph definitions
@AustinMroz AustinMroz requested a review from a team as a code owner February 9, 2026 19:32
@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Feb 9, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This change updates LGraph's node add/remove logic to traverse subgraphs: when adding a subgraph node it registers inner subgraph nodes with the root graph; when removing, it traverses inner nodes to call cleanup hooks and remove their subgraph entries from the root graph.

Changes

Cohort / File(s) Summary
Subgraph traversal & cleanup
src/lib/litegraph/src/LGraph.ts
Added forEachNode traversal on subgraph node addition to register inner subgraphs with this.subgraphs. On node removal, traverse subgraph to call onRemoved/onNodeRemoved, remove inner subgraph entries from the root, and delete the top-level node's subgraph reference.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through nodes both small and grand,
Found hidden nests beneath the land.
I added maps and cleared the slate,
So roots stay tidy, neat, and straight.
Hooray for subgraphs — carrots on my plate! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description addresses the PR objectives and missing template sections. While it explains the issue and changes, it lacks the structured template format with distinct Summary, Changes, and Review Focus sections. Restructure the description to follow the template: add a concise one-sentence summary, organize changes under 'What' and 'Dependencies' sections, and clearly highlight review focus areas for the nested subgraph handling logic.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change, focusing on fixing edge cases in subgraph removal logic, which aligns with the core modifications shown in the code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch austin/fix-nested-subgraph-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 02/09/2026, 09:21:25 PM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Playwright: ✅ 515 passed, 0 failed · 4 flaky

📊 Browser Reports
  • chromium: View Report (✅ 503 / ❌ 0 / ⚠️ 4 / ⏭️ 8)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 9 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/lib/litegraph/src/LGraph.ts`:
- Around line 1045-1053: Normalize the optional chaining and avoid premature
widget cleanup: make the isSubgraphNode usage consistent (either remove the ?.
in the earlier check or add it here) by editing calls to isSubgraphNode so they
match across LGraph (use plain isSubgraphNode() since it exists on LGraphNode),
and remove or defer the call to innerNode.onRemoved?.() inside the forEachNode
loop (where innerNode, innerNode.subgraph, onNodeRemoved and
this.rootGraph.subgraphs are handled) so that widget cleanup hooks are not
invoked during subgraph restructuring; instead only call onRemoved when a node
is being truly deleted from the root graph or add a guard that ensures onRemoved
runs only for permanent removals.

Comment on lines +1045 to 1053
if (node.isSubgraphNode()) {
forEachNode(node.subgraph, (innerNode) => {
innerNode.onRemoved?.()
subgraphNode.subgraph.onNodeRemoved?.(innerNode)
}
this.rootGraph.subgraphs.delete(subgraphNode.subgraph.id)
innerNode.graph?.onNodeRemoved?.(innerNode)
if (innerNode.isSubgraphNode())
this.rootGraph.subgraphs.delete(innerNode.subgraph.id)
})
this.rootGraph.subgraphs.delete(node.subgraph.id)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get context around the problematic lines in LGraph.ts
echo "=== Code context around lines 1045-1053 ==="
sed -n '1040,1060p' src/lib/litegraph/src/LGraph.ts

echo ""
echo "=== Code context around line 977 ==="
sed -n '970,985p' src/lib/litegraph/src/LGraph.ts

echo ""
echo "=== Search for isSubgraphNode definition ==="
rg -n 'isSubgraphNode' src/lib/litegraph --type=ts -A2 -B2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5981


🏁 Script executed:

#!/bin/bash

# Check LGraphNode class definition to understand inheritance and method definitions
echo "=== Searching for LGraphNode class ==="
rg -n 'class LGraphNode' src/lib/litegraph --type=ts -A10

echo ""
echo "=== Check if isSubgraphNode is defined in LGraphNode ==="
ast-grep --pattern 'class LGraphNode {
  $$$
  isSubgraphNode() {
    $$$
  }
  $$$
}'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1779


🏁 Script executed:

#!/bin/bash

# Search for onRemoved implementation and usage
echo "=== Search for onRemoved definition/usage ==="
rg -n 'onRemoved' src/lib/litegraph --type=ts -B2 -A2

echo ""
echo "=== Search for widget.onRemove usage ==="
rg -n 'widget\.onRemove' src/lib/litegraph --type=ts -B2 -A2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 6796


🏁 Script executed:

#!/bin/bash

# Check removeWidget implementation to understand the widget cleanup flow
echo "=== Search for removeWidget method ==="
rg -n 'removeWidget' src/lib/litegraph --type=ts -B2 -A5

echo ""
echo "=== Check widgetValueStore to understand state management ==="
rg -n 'widgetValueStore' src/lib/litegraph --type=ts -B1 -A1

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5153


Harmonize optional chaining usage and reconsider calling onRemoved on inner nodes during subgraph removal.

Line 977 defensively uses node.isSubgraphNode?.() while lines 1045 and 1049 call isSubgraphNode() directly without optional chaining. Since isSubgraphNode() is defined on the base LGraphNode class, the optional chaining is unnecessary but the inconsistency should be resolved—either remove it from line 977 or add it to lines 1045 and 1049 for consistency.

More critically, calling innerNode.onRemoved?.() at line 1047 during subgraph parent removal may trigger widget cleanup handlers (widget.onRemove?.()) that assume permanent deletion. During subgraph conversion operations (convert-to-subgraph, unpack), inner nodes and their widgets may be restructured rather than permanently deleted. If onRemoved hooks unregister widget state from stores, this premature cleanup could cause state loss during legitimate graph restructuring flows. Consider whether inner nodes should have their onRemoved called at all in this context, or defer it until they are truly removed from the root graph.

🤖 Prompt for AI Agents
In `@src/lib/litegraph/src/LGraph.ts` around lines 1045 - 1053, Normalize the
optional chaining and avoid premature widget cleanup: make the isSubgraphNode
usage consistent (either remove the ?. in the earlier check or add it here) by
editing calls to isSubgraphNode so they match across LGraph (use plain
isSubgraphNode() since it exists on LGraphNode), and remove or defer the call to
innerNode.onRemoved?.() inside the forEachNode loop (where innerNode,
innerNode.subgraph, onNodeRemoved and this.rootGraph.subgraphs are handled) so
that widget cleanup hooks are not invoked during subgraph restructuring; instead
only call onRemoved when a node is being truly deleted from the root graph or
add a guard that ensures onRemoved runs only for permanent removals.

@AustinMroz AustinMroz merged commit a6620a4 into main Feb 9, 2026
35 of 39 checks passed
@AustinMroz AustinMroz deleted the austin/fix-nested-subgraph-cleanup branch February 9, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants