Skip to content

fix(core): degrade gracefully when sqlite-vec cannot load on init#774

Merged
phernandez merged 1 commit intomainfrom
fix/graceful-fallback-no-extension-loading
Apr 29, 2026
Merged

fix(core): degrade gracefully when sqlite-vec cannot load on init#774
phernandez merged 1 commit intomainfrom
fix/graceful-fallback-no-extension-loading

Conversation

@phernandez
Copy link
Copy Markdown
Member

Summary

  • When uvx happens to pick a CPython whose sqlite3 was built without extension support (python.org's macOS installer is the documented case), basic-memory's MCP startup crashed with:
    AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'
    
    inside init_search_index → _ensure_vector_tables → _ensure_sqlite_vec_loaded.
  • The crash fires early in startup, before the MCP handshake completes — so Claude Desktop sees Server transport closed unexpectedly and stops trying. The reporter ([BUG] Fatal crash on startup when python.org Python 3.12 is present alongside Homebrew on macOS #711) had to manually clean up to recover.

Change

Two-part fix in src/basic_memory/repository/sqlite_search_repository.py:

  1. _ensure_sqlite_vec_loaded — proactively check hasattr(driver_connection, "enable_load_extension") and raise SemanticDependenciesMissingError with a clear cause + remediation instead of letting the AttributeError bubble up.
  2. init_search_index — wrap the _ensure_vector_tables() call in try/except SemanticDependenciesMissingError, log a warning, and flip self._semantic_enabled = False. Keyword search still works; downstream semantic call sites already short-circuit on _semantic_enabled.

The user now gets a degraded but functional MCP server instead of a hung handshake.

Test plan

  • tests/repository/test_search_repository.py::test_init_search_index_degrades_when_extension_loading_unavailable — patches _ensure_vector_tables to raise, asserts init returns and _semantic_enabled is False.
  • tests/repository/test_search_repository.py::test_ensure_sqlite_vec_loaded_raises_typed_error_without_extension_support — supplies a stub driver connection with no enable_load_extension attribute, asserts the typed error fires.
  • uv run pytest tests/repository/test_search_repository.py tests/services/test_search_service.py tests/services/test_semantic_search.py --no-cov — 106 passed.

Out of scope

  • Migrating the install docs to recommend uv-managed Python over python.org Python on macOS — worth a docs PR but separate.
  • Detecting this earlier (before any DB work) and surfacing a CLI hint — could come later; for now the warning in the log is the signal.

Closes #711

🤖 Generated with Claude Code

When the underlying CPython lacks sqlite extension support (the python.org
macOS installer is the well-known case — its sqlite3 module ships without
enable_load_extension), basic-memory crashed with AttributeError during
init_search_index → _ensure_vector_tables → _ensure_sqlite_vec_loaded,
triggered by `await driver_connection.enable_load_extension(True)`.

That crash happens early in MCP startup, before the handshake with the
client completes — so Claude Desktop sees "Server transport closed
unexpectedly" and gives up permanently until manually restarted (#711).

Two-part fix:

1. _ensure_sqlite_vec_loaded now checks for enable_load_extension before
   calling it and raises a typed SemanticDependenciesMissingError with a
   clear cause/remediation message instead of letting the AttributeError
   surface.

2. init_search_index catches that error, logs a warning, and flips the
   repository to semantic-disabled. Keyword search still works; downstream
   semantic call sites already short-circuit on _semantic_enabled. The
   user gets a degraded but functional MCP server instead of a hung
   handshake.

Closes #711

Signed-off-by: phernandez <paul@basicmachines.co>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 29, 2026

Claude finished @phernandez's task in 1m 52s —— View job


PR Review: fix(core): degrade gracefully when sqlite-vec cannot load on init

  • Gather context and understand the request
  • Read changed files
  • Review against team checklist
  • Post detailed review

Summary: This is a clean, well-targeted fix. Two surgical changes — a hasattr guard in _ensure_sqlite_vec_loaded and a try/except SemanticDependenciesMissingError wrapper in init_search_index — convert a hard crash into a graceful keyword-only degradation. The fix is narrowly scoped, the error messages are genuinely helpful, and the regression tests are well-structured. Approved with one minor observation.


Team Checklist

Code Quality & Standards

  • Follows Basic Memory's coding conventions in CLAUDE.md — Trigger/Why/Outcome literate comments used correctly
  • Python 3.12+ type annotations and async patterns — clean async throughout
  • SQLAlchemy 2.0 best practices — await session.connection() / get_raw_connection() pattern is correct
  • 100-character line length limit maintained — verified
  • No commented-out code blocks

Testing & Documentation

  • Unit tests for new code paths — two targeted regression tests covering both new branches
  • 100% test coverage maintained — except SemanticDependenciesMissingError branch covered by test_init_search_index_degrades_when_extension_loading_unavailable; if not hasattr branch covered by test_ensure_sqlite_vec_loaded_raises_typed_error_without_extension_support; normal success path was already covered by test_init_search_index
  • Documentation — install-guide docs deferred to a follow-up PR, explicitly noted out of scope ✓

Basic Memory Architecture

  • Preserves local-first architecture principles
  • No schema changes / no migration needed
  • _assert_semantic_available() guard in the base class properly short-circuits all downstream semantic call sites once _semantic_enabled = False is set (search_repository_base.py:471)

Security & Performance

  • hasattr check is O(1) and only runs when extension loading is triggered — no measurable overhead
  • No sensitive data in log output
  • No emojis/unicode in console/log output ✓

Compatibility

  • No file path comparisons → Windows safe
  • No emojis/unicode in error messages or log output ✓

Observations

1. Minor — test stub execute lacks **kwargs (tests/repository/test_search_repository.py:169)

class _StubSession:
    async def execute(self, _stmt):
        raise SAOperationalError("SELECT vec_version()", {}, Exception("no vec"))

The actual _ensure_sqlite_vec_loaded only passes one positional arg to execute, so this stub works today. Adding **kwargs would make the stub resilient to any future signature changes in SQLAlchemy without requiring test updates. This is very low priority — noting it for completeness only.

Fix this →

2. Implicit sqlite_vec dependency in the second test (tests/repository/test_search_repository.py:141)

test_ensure_sqlite_vec_loaded_raises_typed_error_without_extension_support reaches the hasattr check only if sqlite_vec can be successfully imported (line 367–372 in the implementation). If sqlite_vec is not installed in a given test environment, the test would still raise SemanticDependenciesMissingError but with the "sqlite-vec package is missing" message — causing the assert "enable_load_extension" in str(exc_info.value) assertion to fail.

Since sqlite_vec is an expected dev dependency this is fine in practice, but it's worth being aware of if the test is ever run in a stripped environment. No action needed.


Overall: This is the right fix — minimal, safe, and correctly maintains the invariant that _semantic_enabled is the single gate for all downstream semantic call sites. The degradation path is coherent: the user gets a working MCP server with keyword search instead of a hung Claude Desktop handshake. ✅

@phernandez phernandez merged commit 2bfb9c7 into main Apr 29, 2026
25 checks passed
@phernandez phernandez deleted the fix/graceful-fallback-no-extension-loading branch April 29, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Fatal crash on startup when python.org Python 3.12 is present alongside Homebrew on macOS

1 participant