From bc37ecf64c0bd60e82c666abbf33275ac4a0a66f Mon Sep 17 00:00:00 2001 From: phernandez Date: Tue, 28 Oct 2025 17:49:06 -0500 Subject: [PATCH] fix: handle project removal for cloud-only projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem:** Cloud projects that exist only in the database (not in config.json) couldn't be removed. The error "Project 'name' not found" occurred because config validation failed before database deletion. **Root cause:** - In cloud mode, projects can exist in database without config entries - ProjectService.remove_project() called config_manager.remove_project() first, which raised ValueError if project wasn't in config - This prevented removal of cloud-only projects **Solution:** 1. Check database first for project existence (source of truth) 2. Validate default project status from both database and config 3. Try to remove from config, but catch ValueError if not found 4. Always remove from database if project exists there **Additional fix:** - Project list Default column now shows in local mode (always) and in cloud mode only if default_project_mode is enabled - This fixes integration tests that expect to see default marker Fixes cloud-only project deletion bug. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Signed-off-by: phernandez --- src/basic_memory/cli/commands/project.py | 9 ++++---- src/basic_memory/services/project_service.py | 24 ++++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/basic_memory/cli/commands/project.py b/src/basic_memory/cli/commands/project.py index c4893845..f2957ffa 100644 --- a/src/basic_memory/cli/commands/project.py +++ b/src/basic_memory/cli/commands/project.py @@ -89,8 +89,9 @@ def list_projects() -> None: if config.cloud_mode_enabled: table.add_column("Local Path", style="yellow", no_wrap=True, overflow="fold") - # Only show Default column if default_project_mode is enabled - if config.default_project_mode: + # Show Default column in local mode or if default_project_mode is enabled in cloud mode + show_default_column = not config.cloud_mode_enabled or config.default_project_mode + if show_default_column: table.add_column("Default", style="magenta") for project in result.projects: @@ -108,8 +109,8 @@ def list_projects() -> None: local_path = format_path(local_path) row.append(local_path) - # Add default indicator if default_project_mode is enabled - if config.default_project_mode: + # Add default indicator if showing default column + if show_default_column: row.append(is_default) table.add_row(*row) diff --git a/src/basic_memory/services/project_service.py b/src/basic_memory/services/project_service.py index aa1d9336..bd011968 100644 --- a/src/basic_memory/services/project_service.py +++ b/src/basic_memory/services/project_service.py @@ -234,16 +234,26 @@ class ProjectService: if not self.repository: # pragma: no cover raise ValueError("Repository is required for remove_project") - # Get project path before removing from config + # Get project from database first project = await self.get_project(name) - project_path = project.path if project else None + if not project: + raise ValueError(f"Project '{name}' not found") - # First remove from config (this will validate the project exists and is not default) - self.config_manager.remove_project(name) + project_path = project.path - # Then remove from database using robust lookup - if project: - await self.repository.delete(project.id) + # Check if project is default (in cloud mode, check database; in local mode, check config) + if project.is_default or name == self.config_manager.config.default_project: + raise ValueError(f"Cannot remove the default project '{name}'") + + # Remove from config if it exists there (may not exist in cloud mode) + try: + self.config_manager.remove_project(name) + except ValueError: + # Project not in config - that's OK in cloud mode, continue with database deletion + logger.debug(f"Project '{name}' not found in config, removing from database only") + + # Remove from database + await self.repository.delete(project.id) logger.info(f"Project '{name}' removed from configuration and database")