Repository navigation
v4.9.2 — Post-review hotfix: rollback atomicity + operating-model docs
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:
_backup_active_indexwas not atomic across its two moves. If thechroma_db/rename succeeded and theindex_metadata.jsonrename then raisedOSError(Windows antivirus scanning the JSON, brief file lock), the process exited (75) withchroma_dbalready inbackups/but metadata still live. Restart then sawchroma_dbgone, incremental reindex loaded the orphan metadata, marked every doc as already indexed, and produced an empty collection silently — no error, no visible failure.- The v4.9.1 handshake-ordering test mocked
start_reindex_backgroundwith 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:
docs/single-instance.mdrecommended leavingKNOWLEDGE_RAG_SINGLE_INSTANCE=1off 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_dbrename 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_metadatalocks the invariant with a 3-callos.renamemock
test(main_startup) — real thread + deadline guard
- Shared
_mock_orchestrator_emptyhelper now spawns a real 2 s daemon thread whenstart_reindex_backgroundis called, matching the v4.9.1 contract - New
test_main_reaches_transport_under_deadlineasserts main() completes in < 0.5 s - If
start_reindex_backgroundever 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 runknowledge-ragin 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
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.