diff --git a/README.md b/README.md index 57a6abd7e..8374c13d2 100644 --- a/README.md +++ b/README.md @@ -479,7 +479,7 @@ multi-word ones. Bare `- [[Target]]` and prose `- Worth checking out [[Target]]` index as `links_to`. Full reference in the -[docs](https://docs.basicmemory.com/getting-started/note-formatting/?utm_source=github&utm_medium=referral&utm_campaign=readme). +[docs](https://docs.basicmemory.com/concepts/knowledge-format?utm_source=github&utm_medium=referral&utm_campaign=readme). ## MCP tools @@ -525,7 +525,7 @@ basic-memory import memory-json Routing flags (`--local` / `--cloud`) force a target when you're in mixed mode. Full CLI reference in the -[docs](https://docs.basicmemory.com/guides/cli-reference/?utm_source=github&utm_medium=referral&utm_campaign=readme). +[docs](https://docs.basicmemory.com/reference/cli-reference?utm_source=github&utm_medium=referral&utm_campaign=readme). ## Auto-updates diff --git a/docs/cloud-cli.md b/docs/cloud-cli.md index ae93ed357..5ba501520 100644 --- a/docs/cloud-cli.md +++ b/docs/cloud-cli.md @@ -32,7 +32,7 @@ The transfer commands fall into two groups: Before using Basic Memory Cloud, you need: - **Active Subscription**: An active Basic Memory Cloud subscription is required to access cloud features -- **Subscribe**: Visit [https://basicmemory.com/subscribe](https://basicmemory.com/subscribe) to sign up +- **Subscribe**: Visit [https://basicmemory.com/pricing](https://basicmemory.com/pricing) to sign up - **Optional**: Cloud is optional. Local-first open-source usage continues without cloud. - **OSS Discount**: Use code `{{OSS_DISCOUNT_CODE}}` for 20% off for 3 months. diff --git a/src/basic_memory/config.py b/src/basic_memory/config.py index 14f74d8a9..61832c773 100644 --- a/src/basic_memory/config.py +++ b/src/basic_memory/config.py @@ -959,8 +959,13 @@ def load_config(self) -> BasicMemoryConfig: # Then overlay with file data for fields that aren't set via env vars # This ensures env vars take precedence - # Get env-based config fields that are actually set - env_config = BasicMemoryConfig() + # Get env-based config fields that are actually set. + # skip_initialization_sync=True keeps this probe side-effect-free: + # model_post_init won't seed a default project, and + # ensure_project_paths_exists won't mkdir. Pydantic Settings still + # reads env vars as field defaults, so env_dict reflects env-derived + # values without writing to the filesystem. See GH#1029. + env_config = BasicMemoryConfig(skip_initialization_sync=True) env_dict = env_config.model_dump() # Merge: file data as base, but only use it for fields not set by env diff --git a/tests/test_config.py b/tests/test_config.py index b6bc34913..1dbc22412 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -366,6 +366,58 @@ def test_config_file_without_default_project_key(self, config_home, monkeypatch) loaded = config_manager.load_config() assert loaded.default_project == "work" + def test_load_config_does_not_recreate_phantom_home_dir( + self, config_home, monkeypatch, tmp_path + ): + """Regression for GH#1029: load_config must not recreate ~/basic-memory. + + When BASIC_MEMORY_HOME is unset, loading a config that already specifies + a user-chosen project path must not also seed the default ~/basic-memory + directory as a side effect of probing env vars. + """ + import json + import basic_memory.config + + # HOME is set by config_home; phantom lives at $HOME/basic-memory. + monkeypatch.delenv("BASIC_MEMORY_HOME", raising=False) + monkeypatch.delenv("BASIC_MEMORY_CLOUD_MODE", raising=False) + + phantom = Path(os.environ["HOME"]) / "basic-memory" + user_project = config_home / "user" / "vault" + # Force the phantom path to live inside tmp_path so the test never touches + # the real $HOME (the user may have a real ~/basic-memory vault). + # We do this by repointing Path.home() to a fresh tmp dir. + phantom_home = tmp_path / "real_home" + phantom = phantom_home / "basic-memory" + monkeypatch.setattr(Path, "home", classmethod(lambda cls: phantom_home)) + + config_manager = ConfigManager() + config_manager.config_dir = config_home / ".basic-memory" + config_manager.config_file = config_manager.config_dir / "config.json" + config_manager.config_dir.mkdir(parents=True, exist_ok=True) + + config_data = { + "projects": {"main": {"path": str(user_project), "mode": "local"}}, + "default_project": "main", + } + config_manager.config_file.write_text(json.dumps(config_data, indent=2)) + basic_memory.config._CONFIG_CACHE = None + basic_memory.config._CONFIG_MTIME = None + basic_memory.config._CONFIG_SIZE = None + + assert not phantom.exists() + + loaded = config_manager.load_config() + + # User's project is preserved — never replaced by a phantom seed. + assert Path(loaded.projects["main"].path) == user_project + assert loaded.default_project == "main" + # The phantom ~/basic-memory dir must not have been created as a side effect. + assert not phantom.exists(), ( + f"load_config recreated {phantom} as a side effect of probing env vars " + f"(see GH#1029)" + ) + class TestDataDirHelpers: """Module-level helpers that resolve the Basic Memory data directory."""