Skip to content

Commit fd732aa

Browse files
phernandezclaude
andauthored
fix: set_default_project skips config file update in cloud mode (#486)
Signed-off-by: phernandez <[email protected]> Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 537e58a commit fd732aa

File tree

4 files changed

+27
-43
lines changed

4 files changed

+27
-43
lines changed

src/basic_memory/services/project_service.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,15 +283,17 @@ async def set_default_project(self, name: str) -> None:
283283
if not self.repository: # pragma: no cover
284284
raise ValueError("Repository is required for set_default_project")
285285

286-
# First update config file (this will validate the project exists)
287-
self.config_manager.set_default_project(name)
288-
289-
# Then update database using the same lookup logic as get_project
286+
# Look up project in database first to validate it exists
290287
project = await self.get_project(name)
291-
if project:
292-
await self.repository.set_as_default(project.id)
293-
else:
294-
logger.error(f"Project '{name}' exists in config but not in database")
288+
if not project:
289+
raise ValueError(f"Project '{name}' not found")
290+
291+
# Update database
292+
await self.repository.set_as_default(project.id)
293+
294+
# Update config file only in local mode (cloud mode uses database only)
295+
if not self.config_manager.config.cloud_mode:
296+
self.config_manager.set_default_project(name)
295297

296298
logger.info(f"Project '{name}' set as default in configuration and database")
297299

tests/mcp/test_project_context.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ async def test_cloud_mode_requires_project_by_default(self):
1717
mock_config = MagicMock()
1818
mock_config.cloud_mode = True
1919

20-
with patch(
21-
"basic_memory.mcp.project_context.ConfigManager"
22-
) as mock_config_manager:
20+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
2321
mock_config_manager.return_value.config = mock_config
2422

2523
with pytest.raises(ValueError) as exc_info:
@@ -36,9 +34,7 @@ async def test_cloud_mode_allows_discovery_when_enabled(self):
3634
mock_config = MagicMock()
3735
mock_config.cloud_mode = True
3836

39-
with patch(
40-
"basic_memory.mcp.project_context.ConfigManager"
41-
) as mock_config_manager:
37+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
4238
mock_config_manager.return_value.config = mock_config
4339

4440
result = await resolve_project_parameter(project=None, allow_discovery=True)
@@ -53,9 +49,7 @@ async def test_cloud_mode_returns_project_when_specified(self):
5349
mock_config = MagicMock()
5450
mock_config.cloud_mode = True
5551

56-
with patch(
57-
"basic_memory.mcp.project_context.ConfigManager"
58-
) as mock_config_manager:
52+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
5953
mock_config_manager.return_value.config = mock_config
6054

6155
result = await resolve_project_parameter(project="my-project")
@@ -70,9 +64,7 @@ async def test_local_mode_uses_env_var_priority(self):
7064
mock_config = MagicMock()
7165
mock_config.cloud_mode = False
7266

73-
with patch(
74-
"basic_memory.mcp.project_context.ConfigManager"
75-
) as mock_config_manager:
67+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
7668
mock_config_manager.return_value.config = mock_config
7769

7870
with patch.dict(os.environ, {"BASIC_MEMORY_MCP_PROJECT": "env-project"}):
@@ -90,9 +82,7 @@ async def test_local_mode_uses_explicit_project(self):
9082
mock_config.cloud_mode = False
9183
mock_config.default_project_mode = False
9284

93-
with patch(
94-
"basic_memory.mcp.project_context.ConfigManager"
95-
) as mock_config_manager:
85+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
9686
mock_config_manager.return_value.config = mock_config
9787

9888
with patch.dict(os.environ, {}, clear=True):
@@ -112,9 +102,7 @@ async def test_local_mode_uses_default_project(self):
112102
mock_config.default_project_mode = True
113103
mock_config.default_project = "default-project"
114104

115-
with patch(
116-
"basic_memory.mcp.project_context.ConfigManager"
117-
) as mock_config_manager:
105+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
118106
mock_config_manager.return_value.config = mock_config
119107

120108
with patch.dict(os.environ, {}, clear=True):
@@ -132,13 +120,11 @@ async def test_local_mode_returns_none_when_no_resolution(self):
132120
mock_config.cloud_mode = False
133121
mock_config.default_project_mode = False
134122

135-
with patch(
136-
"basic_memory.mcp.project_context.ConfigManager"
137-
) as mock_config_manager:
123+
with patch("basic_memory.mcp.project_context.ConfigManager") as mock_config_manager:
138124
mock_config_manager.return_value.config = mock_config
139125

140126
with patch.dict(os.environ, {}, clear=True):
141127
os.environ.pop("BASIC_MEMORY_MCP_PROJECT", None)
142128
result = await resolve_project_parameter(project=None)
143129

144-
assert result is None
130+
assert result is None

tests/schemas/test_schemas.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ def test_relation_response_with_null_permalink():
106106
"permalink": "test/relation/123",
107107
"relation_type": "relates_to",
108108
"from_entity": {"permalink": None, "file_path": "notes/source-note.md"},
109-
"to_entity": {"permalink": None, "file_path": "notes/target-note.md", "title": "Target Note"},
109+
"to_entity": {
110+
"permalink": None,
111+
"file_path": "notes/target-note.md",
112+
"title": "Target Note",
113+
},
110114
}
111115
relation = RelationResponse.model_validate(data)
112116
# Falls back to file_path directly (not converted to permalink)

tests/services/test_project_service.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ async def test_get_project_method(project_service: ProjectService):
284284
async def test_set_default_project_config_db_mismatch(
285285
project_service: ProjectService, config_manager: ConfigManager
286286
):
287-
"""Test set_default_project when project exists in config but not in database."""
287+
"""Test set_default_project raises error when project exists in config but not in database."""
288288
test_project_name = f"test-mismatch-project-{os.urandom(4).hex()}"
289289
with tempfile.TemporaryDirectory() as temp_dir:
290290
test_root = Path(temp_dir)
@@ -293,8 +293,6 @@ async def test_set_default_project_config_db_mismatch(
293293
# Make sure the test directory exists
294294
os.makedirs(test_project_path, exist_ok=True)
295295

296-
original_default = project_service.default_project
297-
298296
try:
299297
# Add project to config only (not to database)
300298
config_manager.add_project(test_project_name, test_project_path)
@@ -304,17 +302,11 @@ async def test_set_default_project_config_db_mismatch(
304302
db_project = await project_service.repository.get_by_name(test_project_name)
305303
assert db_project is None
306304

307-
# Try to set as default - this should trigger the error log on line 142
308-
await project_service.set_default_project(test_project_name)
309-
310-
# Should still update config despite database mismatch
311-
assert project_service.default_project == test_project_name
305+
# Try to set as default - should raise ValueError since project not in database
306+
with pytest.raises(ValueError, match=f"Project '{test_project_name}' not found"):
307+
await project_service.set_default_project(test_project_name)
312308

313309
finally:
314-
# Restore original default
315-
if original_default:
316-
config_manager.set_default_project(original_default)
317-
318310
# Clean up
319311
if test_project_name in project_service.projects:
320312
config_manager.remove_project(test_project_name)

0 commit comments

Comments
 (0)