Skip to content

fix(core): indexing and vector sync fixes from contributor reports - #1759

Merged
phernandez merged 1 commit into
mainfrom
fix/v024-contributor-bugs
Oct 12, 2026
Merged

phernandez merged 1 commit into
mainfrom
fix/v024-contributor-bugs

Conversation

@phernandez

Copy link
Copy Markdown
Member

Summary

Fixes five bugs reported by @beru-ant-king against current main, for v0.24.0. Each report came with a deterministic reproducer, and each fix has a regression test.

Closes #1733, closes #1734, closes #1741, closes #1742, closes #1743.

#1743: bm reindex crashes on a keyword-only host

A regression from this cycle (f756dc5). When sqlite-vec cannot load, the vector rebuild returns vector_index=None, and the reindex summary passed it to Rich's escape(). That raised TypeError from bm reindex, bm project index and bm project add. The summary now prints Embeddings skipped: semantic search is unavailable on this host, so search is keyword-only.

#1742: one bad frontmatter timestamp keeps a note out of the index

  • Parser. An unreadable created/modified value, such as {{date}}, freeform text or an import's date format, raised and failed the whole file. The timestamp is metadata about the note, so the parser now uses the file-time fallback and logs a warning with the path. Missing and null values already fell back the same way.
  • Reporting. bm reindex printed the number of files queued as "indexed" and exited 0 even when files failed. It now prints N indexed, M failed and lists up to 10 failed files with their reason. The project line reads incomplete, and the command exits 1 so scripts notice.

#1741: a full reindex keeps relations the parser no longer produces

A full reindex of unchanged files republishes relations under the same generation (db_version). Cleanup deleted only rows from older generations, and the upsert skips rows at the same generation. A relation the current parser drops therefore survived every bm reindex --full until the note was edited.

v0.24.0's inline-code wikilink fix (#1332) drops exactly this kind of relation, so upgraders would keep false edges and relation search rows.

Cleanup now gets the (relation_type, to_name) set the publication wrote. It also deletes this entity's rows at the current generation that are not in that set, in id batches under the existing generation fence.

  • No new locks.
  • Two publishers of one generation parse the same bytes, so they agree on the set.

#1734: a stale SQLite prepare publishes an obsolete chunk

Vector prepare reads the source and manifest in a shared session, then applies the plan in a write transaction. Only external indexes re-read and re-planned after taking their lock. On SQLite, a plan read before a newer sync of the same note finished could still be applied, which added an obsolete chunk as a ready vector.

SQLite already serializes prepare writes behind _sqlite_prepare_write_lock. It now re-reads and re-plans under that lock too, through a new _replans_prepare_under_write_lock() hook. Built-in Postgres takes no lock at that point, so a re-read there would only add round trips; it keeps its current behavior.

#1733: an older vector sync clears a newer deferred-shard marker

record_entity_vector_deferrals cleared vector_sync_deferred_at for completed entities without any condition. An older pass that finished after a newer pass deferred the same entity erased the only signal that the newer source still had shards to embed.

Each pass now records when it started, and the clear applies only to markers that are null or older than that time. Marking an entity unfinished is unchanged, because over-marking is safe: the next pass clears it. The clear binds NULL in place of :deferred_at, so every marker statement stays within the existing bind-parameter bound.

Verification

Each reporter's reproducer was run against main and against this branch:

Issue main This branch
#1741 (with --assert-cleanup) stale Hidden edge survives only Visible remains
#1734 2 ready chunks for 1 expected 1 chunk, 0 extra
#1733 marker cleared, no deferred target marker kept, entity 1 listed as deferred work

Regression tests:

  • test_reindex_on_keyword_only_host_reports_skipped_embeddings
  • test_reindex_reports_failed_files_and_exits_one
  • test_parse_invalid_canonical_timestamp_falls_back_to_file_time (replaces the test that required a raise)
  • test_same_generation_republish_removes_relations_it_no_longer_writes
  • test_sqlite_stale_prefetched_prepare_does_not_publish_an_obsolete_chunk, which fails on main with 2 manifest rows
  • test_an_older_pass_does_not_clear_a_newer_deferral_marker

Test plan

  • just fast-check
  • just test-unit-sqlite: 8316 passed, 59 skipped
  • just test-int-sqlite: 712 passed, 15 skipped
  • Postgres (testcontainers): relation repository, relation persistence, project readiness, Postgres search repository and vector sync tests: 146 passed
  • Full CI

🤖 Generated with Claude Code

https://claude.ai/code/session_01EQPZkbnuxxtTEJoNJCkt45

…ports

- #1743: bm reindex on a keyword-only host printed None through Rich escape()
  and crashed; it now reports embeddings as skipped.
- #1742: an unreadable created/modified frontmatter value fell the whole note
  out of the index; it now falls back to the file timestamp with a warning.
  reindex also counts and lists files that failed to index and exits 1.
- #1741: a full reindex republishes unchanged notes under the same relation
  generation, so cleanup never removed a relation the current parser no
  longer produces. Cleanup now deletes same-generation rows the publication
  did not write.
- #1734: SQLite applied a prepare plan read before a newer sync finished,
  publishing an obsolete chunk as ready. It now re-plans under its prepare
  write lock, as external indexes already do.
- #1733: an older completed vector sync cleared a newer pass's deferred-shard
  marker. The clear now applies only to markers set before the pass started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EQPZkbnuxxtTEJoNJCkt45
Signed-off-by: phernandez <paul@basicmachines.co>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T03:12:16.357777Z 2543697 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@phernandez
phernandez merged commit 1855224 into main Oct 12, 2026
32 checks passed
@phernandez
phernandez deleted the fix/v024-contributor-bugs branch October 12, 2026 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment