Skip to content

v4.9.2 — Post-review hotfix: rollback atomicity + operating-model docs

Choose a tag to compare

@lyonzin lyonzin released this 29 Sep 14:17
· 34 commits to master since this release
3a96fc6

Post-review hotfix for #216

Ships the CodeRabbit + Greptile review findings on #217 plus the docs rewrite that closes the doc gap @HillsCloud flagged in the operating-model question. Every 4.9.x consumer should upgrade — no API changes, no migration.

What was broken

Two independent problems that survived v4.9.1:

  1. _backup_active_index was not atomic across its two moves. If the chroma_db/ rename succeeded and the index_metadata.json rename then raised OSError (Windows antivirus scanning the JSON, brief file lock), the process exited (75) with chroma_db already in backups/ but metadata still live. Restart then saw chroma_db gone, incremental reindex loaded the orphan metadata, marked every doc as already indexed, and produced an empty collection silently — no error, no visible failure.
  2. The v4.9.1 handshake-ordering test mocked start_reindex_background with a lambda that returned instantly. A hypothetical refactor collapsing the wrapper to synchronous would still pass the call-order assertion. Regression window against the fix's own contract.

And one docs problem:

  1. docs/single-instance.md recommended leaving KNOWLEDGE_RAG_SINGLE_INSTANCE=1 off in setups with Claude Desktop + IDE + terminal. Correct for read-only consumers, wrong for writers — the ChromaDB HNSW segment format is not safe under concurrent writes on Windows, which is exactly what caused the corruption in #216. Nothing in the README made this clear before your first launch.

What was fixed

fix(preflight) — rollback metadata rename on chroma_db failure

  • Inverted order: metadata rename first (small file, low lock probability), chroma_db second
  • chroma_db rename wrapped in try/except that rolls the metadata move back before re-raising
  • Restart sees pristine pre-quarantine state and re-runs preflight cleanly
  • New test test_chroma_rename_failure_rolls_back_metadata locks the invariant with a 3-call os.rename mock

test(main_startup) — real thread + deadline guard

  • Shared _mock_orchestrator_empty helper now spawns a real 2 s daemon thread when start_reindex_background is called, matching the v4.9.1 contract
  • New test_main_reaches_transport_under_deadline asserts main() completes in < 0.5 s
  • If start_reindex_background ever regresses to synchronous, main() blocks on the daemon and the timing assertion fails

docs(operating-model) — MCP stdio ownership + terminal warning

  • New top section in docs/single-instance.md: The operating model — who runs the server. Explains MCP stdio = 1-process-per-client, spawned/killed by the client, watcher runs inside the child process. You do NOT run knowledge-rag in a terminal in parallel.
  • Rewrote When you should NOT enable it → When it is safe to leave the flag unset with an explicit read-only-only caveat and a Windows note on the HNSW concurrent-writer failure mode.
  • New section: Administrative commands (manual operations) — documents the correct sequence for CLI work: close MCP client → run CLI → reopen client.
  • README callout right below the MCP integration table so new users see the constraint before their first launch.

Compatibility

  • Zero API surface changes (check_api_surface --check → OK)
  • Forward-compatible with 4.9.0 and 4.9.1 — no config migration, no data migration
  • 9-cell CI matrix green: Linux + Windows + macOS × Python 3.11 + 3.12 + 3.13

Full changelog

See CHANGELOG.md → v4.9.2

Related PRs

  • #219 — rollback atomicity + test hardening
  • #220 — operating-model docs rewrite
  • #221 — release packaging

Thanks again @HillsCloud — your follow-up question is what surfaced the doc gap that closed the loop on this fix.