Summary
On every config load, Basic Memory recreates an empty ~/basic-memory directory even when the user has configured a completely different project path (e.g. ~/Documents/BasicMemory) and no BASIC_MEMORY_HOME is set. The directory reappears after nearly every bm command and each MCP server startup.
The recreated directory is always empty and unused — real notes/DB live at the correctly-configured path — so this is a cosmetic-but-persistent surprise rather than data loss.
Reproduction
Config (~/.basic-memory/config.json) points main at a custom path:
"projects": { "main": { "path": "/Users/me/Documents/BasicMemory", "mode": "local" } },
"default_project": "main"
Then:
rm -rf ~/basic-memory
bm reindex # or: bm project list (anything that loads config)
ls -d ~/basic-memory # -> exists again, empty
bm --version # does NOT recreate it (never loads config)
bm project list still correctly reports only main -> ~/Documents/BasicMemory; the ~/basic-memory dir is a phantom.
Root cause
The culprit is a filesystem side effect in a Pydantic validator, triggered by a throwaway config instance. In src/basic_memory/config.py (line numbers from current main):
ConfigManager.load_config() builds a throwaway config only to read env-var defaults, passing no file data (~line 612):
env_config = BasicMemoryConfig()
env_dict = env_config.model_dump()
- With no file data and
BASIC_MEMORY_HOME unset, model_post_init injects a default project pointing at ~/basic-memory (~lines 505-509):
if not self.projects:
self.projects["main"] = ProjectEntry(
path=str(Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory"))))
- The
@model_validator(mode="after") ensure_project_paths_exists then mkdirs that path as a side effect (~line 545):
So merely instantiating BasicMemoryConfig() to inspect env defaults writes a directory to disk — against a default path the user never selected.
Suggested fixes (any one breaks the chain)
- Don't create directories inside a
model_validator. Move ensure_project_paths_exists's mkdir into an explicit initialization step (the code path that actually commits/uses a project), so constructing a config has no filesystem effect.
- Or make the env-default probe side-effect-free — read env fields without triggering
model_post_init/after-validators (e.g. a dedicated defaults helper), rather than constructing a full BasicMemoryConfig().
Environment
- basic-memory 0.22.1 (latest release; Homebrew install, Python 3.14)
- macOS
BASIC_MEMORY_HOME unset; single local project at a custom path
Workaround
Setting export BASIC_MEMORY_HOME=<real vault path> stops the recreation, because the throwaway default then points at an already-existing directory and the mkdir becomes a no-op.
Summary
On every config load, Basic Memory recreates an empty
~/basic-memorydirectory even when the user has configured a completely different project path (e.g.~/Documents/BasicMemory) and noBASIC_MEMORY_HOMEis set. The directory reappears after nearly everybmcommand and each MCP server startup.The recreated directory is always empty and unused — real notes/DB live at the correctly-configured path — so this is a cosmetic-but-persistent surprise rather than data loss.
Reproduction
Config (
~/.basic-memory/config.json) pointsmainat a custom path:Then:
bm project liststill correctly reports onlymain -> ~/Documents/BasicMemory; the~/basic-memorydir is a phantom.Root cause
The culprit is a filesystem side effect in a Pydantic validator, triggered by a throwaway config instance. In
src/basic_memory/config.py(line numbers from currentmain):ConfigManager.load_config()builds a throwaway config only to read env-var defaults, passing no file data (~line 612):BASIC_MEMORY_HOMEunset,model_post_initinjects a default project pointing at~/basic-memory(~lines 505-509):@model_validator(mode="after")ensure_project_paths_existsthenmkdirs that path as a side effect (~line 545):So merely instantiating
BasicMemoryConfig()to inspect env defaults writes a directory to disk — against a default path the user never selected.Suggested fixes (any one breaks the chain)
model_validator. Moveensure_project_paths_exists'smkdirinto an explicit initialization step (the code path that actually commits/uses a project), so constructing a config has no filesystem effect.model_post_init/after-validators (e.g. a dedicated defaults helper), rather than constructing a fullBasicMemoryConfig().Environment
BASIC_MEMORY_HOMEunset; single local project at a custom pathWorkaround
Setting
export BASIC_MEMORY_HOME=<real vault path>stops the recreation, because the throwaway default then points at an already-existing directory and themkdirbecomes a no-op.