What happened
On chromadb 1.5.9 with stock defaults and a 2,000-record persistent collection, deleting 100 records by ID leaves all 100 of their vectors bitwise intact in data_level0.bin. The index file is byte-identical (SHA-256) before and after the delete, and the erased IDs remain in the segment's id_to_label map, so a direct read of the file recovers 2,000 vectors from a collection that reports 1,900.
Running chroma vacuum --path <store> --force (exit 0) then clears those records' identifiers from chroma.sqlite3 completely, while leaving all 100 vectors in place. That combination is the part I think is worth your attention: an operator who processes a deletion request, runs the documented cleanup, and verifies with SQL sees a clean store and still has the data on disk. delete_collection() likewise leaves the populated segment directory behind.
Reproduction
Attached: the script, the pinned environment, and the raw results for all three runs. Every figure reproduced identically across three runs (Python 3.13.5, Windows, fixed seed).
chroma-erasure-repro-2026-08.zip
Two things I want to be straight about
This is not new: Ghost Vectors (arXiv:2606.18497, June 2026) reported the underlying behaviour and tested Chroma among others. My contribution is reproducing it on the current release with stock defaults and testing the cleanup paths specifically.
And credit where it is due: on my store the write-ahead-log retention from #3793 did not reproduce. I measured automatically_purge on by default and zero surviving rows, which matches the threshold explanation worked out in #7659: my store is well past hnsw:sync_threshold, so the purge has fired. This issue is about a different file. The HNSW index is not rewritten by the delete, by vacuum, or by delete_collection, at any store size.
I am not reporting this as a vulnerability and I am not asking for a CVE. It reads to me as a documentation and expectation gap around what vacuum covers.
Disclosure
I am publishing a write-up of this today, 31 August 2026, in a monthly report on erasure in vector databases. Since the behaviour has been public since June 2026 via the paper above, and nothing here is an exploit, I am filing and publishing on the same day rather than holding the report. Happy to correct anything I have got wrong, and I will note it in the report if you tell me a fix is coming.
What happened
On chromadb 1.5.9 with stock defaults and a 2,000-record persistent collection, deleting 100 records by ID leaves all 100 of their vectors bitwise intact in
data_level0.bin. The index file is byte-identical (SHA-256) before and after the delete, and the erased IDs remain in the segment'sid_to_labelmap, so a direct read of the file recovers 2,000 vectors from a collection that reports 1,900.Running
chroma vacuum --path <store> --force(exit 0) then clears those records' identifiers fromchroma.sqlite3completely, while leaving all 100 vectors in place. That combination is the part I think is worth your attention: an operator who processes a deletion request, runs the documented cleanup, and verifies with SQL sees a clean store and still has the data on disk.delete_collection()likewise leaves the populated segment directory behind.Reproduction
Attached: the script, the pinned environment, and the raw results for all three runs. Every figure reproduced identically across three runs (Python 3.13.5, Windows, fixed seed).
chroma-erasure-repro-2026-08.zip
Two things I want to be straight about
This is not new: Ghost Vectors (arXiv:2606.18497, June 2026) reported the underlying behaviour and tested Chroma among others. My contribution is reproducing it on the current release with stock defaults and testing the cleanup paths specifically.
And credit where it is due: on my store the write-ahead-log retention from #3793 did not reproduce. I measured
automatically_purgeon by default and zero surviving rows, which matches the threshold explanation worked out in #7659: my store is well pasthnsw:sync_threshold, so the purge has fired. This issue is about a different file. The HNSW index is not rewritten by the delete, byvacuum, or bydelete_collection, at any store size.I am not reporting this as a vulnerability and I am not asking for a CVE. It reads to me as a documentation and expectation gap around what
vacuumcovers.Disclosure
I am publishing a write-up of this today, 31 August 2026, in a monthly report on erasure in vector databases. Since the behaviour has been public since June 2026 via the paper above, and nothing here is an exploit, I am filing and publishing on the same day rather than holding the report. Happy to correct anything I have got wrong, and I will note it in the report if you tell me a fix is coming.