fix(core): honor BASIC_MEMORY_CONFIG_DIR across remaining call sites#744
fix(core): honor BASIC_MEMORY_CONFIG_DIR across remaining call sites#744phernandez merged 6 commits intomainfrom
Conversation
FastEmbed's own default caches ONNX model artifacts under <system tmp>/fastembed_cache. In sandboxed MCP runtimes (e.g. Codex CLI) that directory is wiped between invocations, so every semantic search raised NO_SUCHFILE once the first run ended. Resolve the cache dir to <basic-memory data dir>/fastembed_cache (or FASTEMBED_CACHE_PATH when set) at provider construction time. Keep the config field nullable with default=None so nothing gets baked into config.json — that way later changes to BASIC_MEMORY_CONFIG_DIR or FASTEMBED_CACHE_PATH take effect on every run. The provider cache key uses the resolved path so env-driven changes correctly invalidate the cached provider. Also extract resolve_data_dir() as the single source of truth for the per-user data directory and point BasicMemoryConfig.data_dir_path and ConfigManager.__init__ at it (removes two existing duplications). Fixes #741 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
- Use explicit ``is not None`` instead of ``or`` in _resolve_cache_dir so
an empty-string override doesn't silently fall through to the default.
Empty string is an invalid path; letting FastEmbed surface the error
is clearer than swapping it out for ~/.basic-memory/fastembed_cache.
- Drop the ``os.getenv("HOME", Path.home())`` dance in resolve_data_dir.
``Path.home()`` already reads $HOME on POSIX and %USERPROFILE% on
Windows, so the env check was redundant and made tests that patched
os.name to simulate Windows on non-Windows hosts fragile.
- Switch default_fastembed_cache_dir to ``os.getenv`` for consistency
with the rest of config.py.
- Fix test_default_fastembed_cache_dir_never_falls_back_to_tmp so it no
longer mis-fires on Linux CI. ``tempfile.gettempdir()`` is ``/tmp``
there and pytest's ``tmp_path`` lives under it, so the previous
``startswith(tempfile.gettempdir())`` guard flagged legitimate paths.
Compare exact paths against FastEmbed's concrete
``<tempdir>/fastembed_cache`` fallback instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Claude finished @groksrc's task in 2m 35s —— View job PR Review: fix(core): honor BASIC_MEMORY_CONFIG_DIR across remaining call sites
SummaryThis is a clean, well-scoped follow-up to #743. Each of the five hardcoded Overall: LGTM with minor feedback below. Code Quality Checklist
Findings1. Stale docstring —
|
Hundreds of ``test-int/`` tests were failing on Windows CI with ``Project not found: 'main'`` after resolve_data_dir() was simplified to use ``Path.home()`` directly (#743). Root cause: ``Path.home()`` reads ``$HOME`` on POSIX and ``%USERPROFILE%`` on Windows. The ``test-int/conftest.py`` ``config_home`` fixture only monkeypatched ``HOME``, which worked before because ``ConfigManager.data_dir_path`` used ``os.getenv("HOME", Path.home())`` — that preferred ``HOME`` even on Windows. Now that ``Path.home()`` is called directly, ``USERPROFILE`` wins and the fixture redirects nothing, so the test data dir falls back to the real user home where no ``main`` project is configured. Mirror the pattern already used in ``tests/conftest.py:config_home``: when running on Windows, also set ``USERPROFILE`` to ``tmp_path`` so ``Path.home()`` sees the redirected location. No-op on POSIX. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
Several modules were hardcoding ``Path.home() / ".basic-memory"`` when
resolving Basic Memory state, which ignored ``BASIC_MEMORY_CONFIG_DIR``
and caused split state when users isolated config and the database
under a redirected data directory:
- utils.setup_logging — log file
- ignore_utils.get_bmignore_path — .bmignore
- sync.watch_service.WatchService.status_path — watch-status.json
- services.project_service.get_system_status — watch-status.json read
- cli.commands.cloud.rclone_commands.get_project_bisync_state — bisync
state dir
Route each call site through resolve_data_dir() (or the already-threaded
config, where available) so all of these follow the same override.
Also simplify resolve_data_dir() to use Path.home() directly. Path.home()
already reads $HOME on POSIX and %USERPROFILE% on Windows, so the explicit
os.getenv("HOME", ...) dance was redundant and made tests fragile when
they patched os.name to simulate Windows on non-Windows hosts.
Adds regression tests for each call site that exercise the redirected
BASIC_MEMORY_CONFIG_DIR path.
Fixes #742
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
269b0b9 to
e39b8e1
Compare
``test_setup_logging_honors_basic_memory_config_dir`` was monkeypatching
``utils.os.name`` to ``"posix"`` to force ``setup_logging`` onto the
shared-log-filename branch, which let the test assert on the exact
path. That globally redirected ``os.name``, and on Windows CI that
broke ``Path(str)`` dispatch inside ``resolve_data_dir``:
pathlib.UnsupportedOperation: cannot instantiate 'PosixPath' on
your system
The regression guard only cares that the log file lives under the
redirected ``BASIC_MEMORY_CONFIG_DIR`` — not at ``Path.home()``. It
does not care whether the filename is the shared ``basic-memory.log``
(POSIX) or the per-process ``basic-memory-<pid>.log`` (Windows).
Drop the ``os.name`` patch, assert on ``log_path.parent`` equalling
the custom dir, and sanity-check the filename pattern without pinning
the pid suffix. Same intent, portable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Claude finished @groksrc's task in 2m 47s —— View job PR Review: fix(core): honor BASIC_MEMORY_CONFIG_DIR across remaining call sites
What changed in the latest commit (
|
| Item | Status |
|---|---|
| Follows Basic Memory coding conventions | ✅ |
| Python 3.12+ type annotations and async patterns | ✅ |
| FastAPI / Typer conventions followed | ✅ |
| 100-char line length maintained | ✅ |
| No commented-out code | ✅ |
| Unit tests for all 5 changed call sites | ✅ |
| Edge-case coverage (env var absent) | ✅ |
| 100% test coverage claim is accurate | # pragma: no cover on now-exercised lines (see below) |
| Documentation / docstrings updated | |
| Preserves local-first architecture | ✅ |
| No hardcoded secrets | ✅ |
| Windows path compatibility | ✅ (latest commit fixed the test) |
| No emojis/unicode in console/log output | ✅ |
Outstanding Findings (3 items, carried from previous reviews — not yet addressed)
1. Stale docstring — utils.py:265
# Current (stale)
log_to_file: Write to ~/.basic-memory/basic-memory.log with rotation
# Should be
log_to_file: Write to <basic-memory data dir>/basic-memory.log with rotation (honors BASIC_MEMORY_CONFIG_DIR)2. Stale docstring — ignore_utils.py:183
# Current (stale)
1. ~/.basic-memory/.bmignore (user's global ignore patterns)
# Should be
1. <basic-memory data dir>/.bmignore (user's global ignore patterns, honors BASIC_MEMORY_CONFIG_DIR)3. # pragma: no cover on now-exercised lines — project_service.py:1141–1147
if watch_status_path.exists():
try: # pragma: no cover ← now exercised — remove this
watch_status = json.loads( # pragma: no cover ← now exercised — remove this
watch_status_path.read_text(encoding="utf-8")
)
except Exception: # pragma: no cover ← keep — still hard to test
passtest_get_system_status_reads_watch_status_from_config_dir creates the watch-status file and calls get_system_status(), so the try: / json.loads / .read_text lines are exercised. Remove # pragma: no cover from those two lines only; leave it on except Exception:.
Note: I attempted to push these three fixes directly but lacked write access to the branch (403). The changes are minimal and mechanical — any of the "Fix this →" links above will apply them via Claude Code.
Overall: The five functional changes are correct and well-tested. The latest commit correctly resolves the Windows test-compatibility issue. Three minor cleanup items remain before merge.
- utils.setup_logging: update stale docstring to reflect the new log path (``<basic-memory data dir>/basic-memory.log``, honors BASIC_MEMORY_CONFIG_DIR). The implementation was fixed but the ``Args:`` block still referenced ``~/.basic-memory/``. - ignore_utils.load_gitignore_patterns: same treatment — the "combines patterns from" bullet list still called out ``~/.basic-memory/.bmignore``. - project_service.get_system_status: drop ``# pragma: no cover`` from the ``try`` and ``json.loads(...)`` lines. The new ``test_get_system_status_reads_watch_status_from_config_dir`` test writes a valid watch-status.json and asserts on the parsed payload, so those lines are now exercised. Keeps the pragma on the ``except Exception: pass`` branch, which is still hard to test without injecting a read failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Claude finished @groksrc's task in 58s —— View job PR Review: fix(core): honor BASIC_MEMORY_CONFIG_DIR across remaining call sites
What changed in the latest commit (
|
| Finding | Status |
|---|---|
Stale docstring utils.py:265 |
✅ Fixed — now reads <basic-memory data dir>/basic-memory.log with rotation (honors BASIC_MEMORY_CONFIG_DIR) |
Stale docstring ignore_utils.py:183 |
✅ Fixed — now reads <basic-memory data dir>/.bmignore (user's global ignore patterns, honors BASIC_MEMORY_CONFIG_DIR) |
# pragma: no cover on now-exercised lines project_service.py:1142–1143 |
✅ Fixed — pragmas removed from try: and json.loads lines; except Exception: retains its annotation |
Code Quality Checklist
| Item | Status |
|---|---|
| Follows Basic Memory coding conventions | ✅ |
| Python 3.12+ type annotations and async patterns | ✅ |
| FastAPI / Typer conventions followed | ✅ |
| 100-char line length maintained | ✅ |
| No commented-out code | ✅ |
| Unit tests for all 5 changed call sites | ✅ |
| Edge-case coverage (env var absent) | ✅ |
| 100% test coverage claim is accurate | ✅ (pragmas cleaned up) |
| Documentation / docstrings updated | ✅ (both stale docstrings corrected) |
| Preserves local-first architecture | ✅ |
| No hardcoded secrets | ✅ |
| Windows path compatibility | ✅ |
| No emojis/unicode in console/log output | ✅ |
Summary
All previous findings resolved. The PR is clean:
- Five hardcoded
Path.home() / ".basic-memory"call sites now correctly route throughresolve_data_dir()or the threaded config object - Five regression tests (all named
*_honors_basic_memory_config_dir) guard each fix - Deferred import in
setup_loggingcorrectly handles theconfig.py → utils.pycircular-import constraint - Two docstrings updated to reflect the new behavior
# pragma: no covercleaned up to match actual coverage
LGTM — ready to merge.
Summary
Follow-up to #741 / #743. Several modules were hardcoding
Path.home() / ".basic-memory"when resolving Basic Memory state, which ignoredBASIC_MEMORY_CONFIG_DIRand caused split state when users isolated config and the database under a redirected data directory:utils.setup_logging— log fileignore_utils.get_bmignore_path—.bmignoresync.watch_service.WatchService.status_path—watch-status.jsonservices.project_service.get_system_status—watch-status.jsonreadcli.commands.cloud.rclone_commands.get_project_bisync_state— bisync state dirRoute each call site through
resolve_data_dir()(introduced in #743) or the already-threaded config where one is available, so all of these follow the same override.Fixes #742.
What changed
src/basic_memory/utils.py—setup_logging()now usesresolve_data_dir() / log_filenamevia a deferred import (avoids theconfig.py → utils.pycycle).src/basic_memory/ignore_utils.py—get_bmignore_path()usesresolve_data_dir() / ".bmignore".src/basic_memory/sync/watch_service.py—WatchService.status_pathusesapp_config.data_dir_path / WATCH_STATUS_JSON.src/basic_memory/services/project_service.py—get_system_status()usesself.config_manager.config.data_dir_path / WATCH_STATUS_JSON.src/basic_memory/cli/commands/cloud/rclone_commands.py—get_project_bisync_state()usesresolve_data_dir() / "bisync-state" / project_name.Test plan
*_honors_basic_memory_config_dirfor easy grep:test_setup_logging_honors_basic_memory_config_dirtest_get_bmignore_path_honors_basic_memory_config_dir(+ default-path coverage)test_watch_service_status_path_honors_basic_memory_config_dirtest_get_system_status_reads_watch_status_from_config_dirtest_get_project_bisync_state_honors_basic_memory_config_dirtest_setup_logging_*Windows/POSIX tests andtest_get_project_bisync_stateto clearBASIC_MEMORY_CONFIG_DIRexplicitly, so their behavior doesn't depend on ambient env state.tests/sync,tests/services,tests/utils,tests/test_config.py(broader sanity check).🤖 Generated with Claude Code