From e13da8104c1314d467272ab1a1e46b84d1180002 Mon Sep 17 00:00:00 2001 From: xmarre Date: Wed, 18 Mar 2026 12:26:30 +0100 Subject: [PATCH 1/2] Fix shallow is_changed handling --- comfy_execution/caching.py | 18 ++++++++++-------- tests/execution/test_caching.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/comfy_execution/caching.py b/comfy_execution/caching.py index fecf54d1e..398036eb8 100644 --- a/comfy_execution/caching.py +++ b/comfy_execution/caching.py @@ -68,20 +68,22 @@ _FAILED_SIGNATURE = object() def _shallow_is_changed_signature(value): - """Sanitize execution-time `is_changed` values with a small fail-closed budget.""" + """Reduce execution-time `is_changed` values without deep traversal.""" value_type = type(value) if value_type in _PRIMITIVE_SIGNATURE_TYPES: return value - canonical = to_hashable(value, max_nodes=64) - if type(canonical) is Unhashable: - return canonical - if value_type is list or value_type is tuple: - container_tag = "is_changed_list" if value_type is list else "is_changed_tuple" - return (container_tag, canonical[1]) + try: + items = tuple(value) + except RuntimeError: + return Unhashable() + if all(type(item) in _PRIMITIVE_SIGNATURE_TYPES for item in items): + container_tag = "is_changed_list" if value_type is list else "is_changed_tuple" + return (container_tag, items) + return Unhashable() - return canonical + return Unhashable() def _primitive_signature_sort_key(obj): diff --git a/tests/execution/test_caching.py b/tests/execution/test_caching.py index 569bf5bd8..db6d95063 100644 --- a/tests/execution/test_caching.py +++ b/tests/execution/test_caching.py @@ -25,6 +25,34 @@ class _StubNode: return {"required": {}} +def test_shallow_is_changed_signature_keeps_primitive_only_list_shallow(): + assert caching._shallow_is_changed_signature([1, "two", None, True]) == ( + "is_changed_list", + (1, "two", None, True), + ) + + +def test_shallow_is_changed_signature_keeps_primitive_only_tuple_shallow(): + assert caching._shallow_is_changed_signature((1, "two", None, True)) == ( + "is_changed_tuple", + (1, "two", None, True), + ) + + +def test_shallow_is_changed_signature_fails_closed_for_nested_container(monkeypatch): + monkeypatch.setattr( + caching, + "to_hashable", + lambda *_args, **_kwargs: (_ for _ in ()).throw( + AssertionError("is_changed signature must not deep-canonicalize") + ), + ) + + signature = caching._shallow_is_changed_signature([1, [2, 3]]) + + assert isinstance(signature, caching.Unhashable) + + def test_get_immediate_node_signature_canonicalizes_non_link_inputs(monkeypatch): live_value = [1, {"nested": [2, 3]}] dynprompt = _StubDynPrompt( From c702cddf754a4fc0a61b046d7f80a730323b6811 Mon Sep 17 00:00:00 2001 From: xmarre Date: Wed, 18 Mar 2026 13:15:04 +0100 Subject: [PATCH 2/2] Fix shallow is_changed logic --- comfy_execution/caching.py | 20 ++++++++++---------- tests/execution/test_caching.py | 22 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/comfy_execution/caching.py b/comfy_execution/caching.py index 398036eb8..1a5615627 100644 --- a/comfy_execution/caching.py +++ b/comfy_execution/caching.py @@ -68,22 +68,22 @@ _FAILED_SIGNATURE = object() def _shallow_is_changed_signature(value): - """Reduce execution-time `is_changed` values without deep traversal.""" + """Reduce execution-time `is_changed` values through a fail-closed builtin canonicalizer.""" value_type = type(value) if value_type in _PRIMITIVE_SIGNATURE_TYPES: return value - if value_type is list or value_type is tuple: - try: - items = tuple(value) - except RuntimeError: - return Unhashable() - if all(type(item) in _PRIMITIVE_SIGNATURE_TYPES for item in items): - container_tag = "is_changed_list" if value_type is list else "is_changed_tuple" - return (container_tag, items) + if value_type not in _CONTAINER_SIGNATURE_TYPES: return Unhashable() - return Unhashable() + canonical = _signature_to_hashable(value, max_nodes=64) + if type(canonical) is Unhashable: + return canonical + if value_type is list or value_type is tuple: + container_tag = "is_changed_list" if value_type is list else "is_changed_tuple" + return (container_tag, canonical[1]) + + return canonical def _primitive_signature_sort_key(obj): diff --git a/tests/execution/test_caching.py b/tests/execution/test_caching.py index db6d95063..765ab0b91 100644 --- a/tests/execution/test_caching.py +++ b/tests/execution/test_caching.py @@ -39,7 +39,17 @@ def test_shallow_is_changed_signature_keeps_primitive_only_tuple_shallow(): ) -def test_shallow_is_changed_signature_fails_closed_for_nested_container(monkeypatch): +def test_shallow_is_changed_signature_keeps_structured_builtin_fingerprint_list(): + assert caching._shallow_is_changed_signature([("seed", 42), {"cfg": 8}]) == ( + "is_changed_list", + ( + ("tuple", ("seed", 42)), + ("dict", (("cfg", 8),)), + ), + ) + + +def test_shallow_is_changed_signature_does_not_use_to_hashable(monkeypatch): monkeypatch.setattr( caching, "to_hashable", @@ -48,9 +58,15 @@ def test_shallow_is_changed_signature_fails_closed_for_nested_container(monkeypa ), ) - signature = caching._shallow_is_changed_signature([1, [2, 3]]) + signature = caching._shallow_is_changed_signature([("seed", 42), {"cfg": 8}]) - assert isinstance(signature, caching.Unhashable) + assert signature == ( + "is_changed_list", + ( + ("tuple", ("seed", 42)), + ("dict", (("cfg", 8),)), + ), + ) def test_get_immediate_node_signature_canonicalizes_non_link_inputs(monkeypatch):