Repository navigation
[BUG] SqliteVecIndex.delete() builds one OR chain and fails above 995 deletions #1850
Description
Bug Description
SqliteVecIndex.delete() (repository/sqlite_vec_index.py:255-280) puts every chunk it deletes into a
single UPDATE … WHERE project_id = :project_id AND (<p0> OR <p1> OR …) RETURNING id statement, one
4-term predicate per chunk and no batching. SQLite's expression-depth limit
(SQLITE_MAX_EXPR_DEPTH, 1000) caps the chain, so a call with more than 995 deletions fails with
Expression tree is too large (maximum depth 1000). It is called once per entity
(repository/search_repository_base.py:862, 874), so one entity with enough stale chunks aborts
the whole operation.
We hit it during a full bm reindex --embeddings (on 0.23.2, where the same method lived at
sqlite_vec_index.py:226): 1 537 pending chunk rows of an already-deleted entity, and the reindex of
that project aborted within 2 s. We removed the rows by hand after a backup, and the re-run passed. The
statement shape is unchanged in 0.24.1.
Steps To Reproduce
The statement shape alone is enough. With the SQLite BM 0.24.1 runs on here (3.53.1, from its own
interpreter):
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE c (id INTEGER PRIMARY KEY, project_id, entity_id, chunk_key, source_hash, embedding_status)")
def run(n):
preds = " OR ".join(f"(entity_id = :e{i} AND chunk_key = :k{i} AND source_hash = :h{i} "
"AND embedding_status = 'pending')" for i in range(n))
params = {"p": 1} | {f"e{i}": 1 for i in range(n)} | {f"k{i}": "k" for i in range(n)} \
| {f"h{i}": "h" for i in range(n)}
db.execute(f"UPDATE c SET source_hash = source_hash WHERE project_id = :p AND ({preds}) RETURNING id",
params).fetchall()
run(995) # ok
run(996) # sqlite3.OperationalError: Expression tree is too large (maximum depth 1000)In BM: any entity with ≥ 996 chunk rows to delete in one delete() call. In our case these were the
leftover pending rows of a deleted entity.
Expected Behavior
Deleting any number of chunks succeeds.
Actual Behavior
OperationalError: Expression tree is too large (maximum depth 1000), and the reindex or sync of that
project aborts.
Environment
- OS: macOS 27.0.1
- Python version: 3.13.17
- Basic Memory version: 0.24.1 (first seen on 0.23.2)
- Installation method: Homebrew
- Claude Desktop version (if applicable): n/a
Additional Context
Separate from #1581, which was the correlated delete_orphans scan (fixed in 0.24). The leftover
pending rows of a deleted entity that triggered it for us may be a bug of their own: the entity was
gone, but its chunk manifest was not.
Possible Solution
Batch the deletions, e.g. 500 per statement, or stage the keys in a temp table and JOIN instead of
an OR chain. The follow-up rowid IN (…) list is a flat list, not a tree, so it is not affected by
the depth limit, but batching it too keeps it under SQLITE_MAX_VARIABLE_NUMBER.
Activity
MbappeWU commentedon Oct 24, 2026
I'll take this: batch SqliteVecIndex.delete() so OR chains stay under SQLite's expression-depth limit, with a regression test above 995 deletions.