From 7718fbae2a080f9b5b971927cbeb344fd5bf7a05 Mon Sep 17 00:00:00 2001 From: Cedric Hurst Date: Sat, 15 Nov 2025 18:15:22 -0600 Subject: [PATCH] fix: respect --project flag in background sync (fixes #434) When starting the MCP server with --project flag, background sync and watch service now correctly constrain to only the specified project instead of syncing all active projects. Changes: - Filter active_projects by BASIC_MEMORY_MCP_PROJECT env var - Add logging when background sync is constrained - Add test to verify project constraint works correctly This fix ensures consistent project isolation across both MCP tools and background infrastructure, resolving resource waste when running with multiple configured projects. Fixes #434 Signed-off-by: Cedric Hurst --- src/basic_memory/services/initialization.py | 7 +++ tests/services/test_initialization.py | 68 +++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/basic_memory/services/initialization.py b/src/basic_memory/services/initialization.py index 7038f8fd..b491a6ad 100644 --- a/src/basic_memory/services/initialization.py +++ b/src/basic_memory/services/initialization.py @@ -5,6 +5,7 @@ """ import asyncio +import os from pathlib import Path from loguru import logger @@ -101,6 +102,12 @@ async def initialize_file_sync( # Get active projects active_projects = await project_repository.get_active_projects() + # Filter to constrained project if MCP server was started with --project + constrained_project = os.environ.get("BASIC_MEMORY_MCP_PROJECT") + if constrained_project: + active_projects = [p for p in active_projects if p.name == constrained_project] + logger.info(f"Background sync constrained to project: {constrained_project}") + # Start sync for all projects as background tasks (non-blocking) async def sync_project_background(project): """Sync a single project in the background.""" diff --git a/tests/services/test_initialization.py b/tests/services/test_initialization.py index 1e7bb873..fbbac703 100644 --- a/tests/services/test_initialization.py +++ b/tests/services/test_initialization.py @@ -178,3 +178,71 @@ async def test_initialize_file_sync_background_tasks( # Watch service should still be started mock_watch_service.run.assert_called_once() + + +@pytest.mark.asyncio +@patch("basic_memory.services.initialization.db.get_or_create_db") +@patch("basic_memory.cli.commands.sync.get_sync_service") +@patch("basic_memory.sync.WatchService") +@patch("basic_memory.services.initialization.asyncio.create_task") +@patch.dict("os.environ", {"BASIC_MEMORY_MCP_PROJECT": "project1"}) +async def test_initialize_file_sync_respects_project_constraint( + mock_create_task, mock_watch_service_class, mock_get_sync_service, mock_get_db, app_config +): + """Test that file sync only syncs the constrained project when BASIC_MEMORY_MCP_PROJECT is set.""" + # Setup mocks + mock_session_maker = AsyncMock() + mock_get_db.return_value = (None, mock_session_maker) + + mock_watch_service = AsyncMock() + mock_watch_service.run = AsyncMock() + mock_watch_service_class.return_value = mock_watch_service + + mock_repository = AsyncMock() + mock_project1 = MagicMock() + mock_project1.name = "project1" + mock_project1.path = "/path/to/project1" + mock_project1.id = 1 + + mock_project2 = MagicMock() + mock_project2.name = "project2" + mock_project2.path = "/path/to/project2" + mock_project2.id = 2 + + mock_project3 = MagicMock() + mock_project3.name = "project3" + mock_project3.path = "/path/to/project3" + mock_project3.id = 3 + + mock_sync_service = AsyncMock() + mock_sync_service.sync = AsyncMock() + mock_get_sync_service.return_value = mock_sync_service + + # Mock background tasks + mock_task = MagicMock() + mock_create_task.return_value = mock_task + + # Mock the repository + with patch("basic_memory.services.initialization.ProjectRepository") as mock_repo_class: + mock_repo_class.return_value = mock_repository + # Return all 3 projects from get_active_projects + mock_repository.get_active_projects.return_value = [ + mock_project1, + mock_project2, + mock_project3, + ] + + # Run the function + result = await initialize_file_sync(app_config) + + # Assertions + mock_repository.get_active_projects.assert_called_once() + + # Should only create 1 background task for project1 (the constrained project) + assert mock_create_task.call_count == 1 + + # Verify the function returns None + assert result is None + + # Watch service should still be started + mock_watch_service.run.assert_called_once()