fix: correct ProjectItem.home property to return path instead of name… · basicmachines-co/basic-memory@3e876a7 · GitHub
Skip to content

Commit 3e876a7

Browse files
phernandezgithub-actions[bot]claude
authored
fix: correct ProjectItem.home property to return path instead of name (#341)
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1fa93ec commit 3e876a7

5 files changed

Lines changed: 143 additions & 83 deletions

File tree

src/basic_memory/api/app.py

Lines changed: 1 addition & 1 deletion

src/basic_memory/schemas/project_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def permalink(self) -> str: # pragma: no cover
183183

184184
@property
185185
def home(self) -> Path: # pragma: no cover
186-
return Path(self.name)
186+
return Path(self.path).expanduser()
187187

188188
@property
189189
def project_url(self) -> str: # pragma: no cover

src/basic_memory/services/project_service.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -138,21 +138,13 @@ async def add_project(self, name: str, path: str, set_default: bool = False) ->
138138
if project_root:
139139
base_path = Path(project_root)
140140

141-
# Sanitize the input path
142-
# Strip leading slashes, home directory references, and parent directory references
143-
clean_path = path.lstrip("/").replace("~/", "").replace("~", "")
144-
145-
# Remove any parent directory traversal attempts and normalize to lowercase
146-
# to prevent case-sensitivity issues on Linux filesystems
147-
path_parts = []
148-
for part in clean_path.split("/"):
149-
if part and part != "." and part != "..":
150-
# Convert to lowercase to ensure case-insensitive consistency
151-
path_parts.append(part.lower())
152-
clean_path = "/".join(path_parts) if path_parts else ""
153-
154-
# Construct path relative to project_root
155-
resolved_path = (base_path / clean_path).resolve().as_posix()
141+
# In cloud mode (when project_root is set), ignore user's path completely
142+
# and use sanitized project name as the directory name
143+
# This ensures flat structure: /app/data/test-bisync instead of /app/data/documents/test bisync
144+
sanitized_name = generate_permalink(name)
145+
146+
# Construct path using sanitized project name only
147+
resolved_path = (base_path / sanitized_name).resolve().as_posix()
156148

157149
# Verify the resolved path is actually under project_root
158150
if not resolved_path.startswith(base_path.resolve().as_posix()):

test-int/mcp/test_write_note_integration.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,61 @@ async def test_write_note_file_path_os_path_join(mcp_server, test_project):
432432

433433
# Restore original config value
434434
config.kebab_filenames = curr_config_val
435+
436+
437+
@pytest.mark.asyncio
438+
async def test_write_note_project_path_validation(mcp_server, test_project):
439+
"""Test that ProjectItem.home uses expanded path, not name (Issue #340).
440+
441+
Regression test verifying that:
442+
1. ProjectItem.home returns Path(self.path).expanduser()
443+
2. Not Path(self.name) which was the bug
444+
445+
This test verifies the fix works correctly even though in the test environment
446+
the project name and path happen to be the same. The fix in src/basic_memory/schemas/project_info.py:186
447+
ensures .expanduser() is called, which is critical for paths with ~ like "~/Documents/Test BiSync".
448+
"""
449+
from basic_memory.schemas.project_info import ProjectItem
450+
from pathlib import Path
451+
452+
# Test the fix directly: ProjectItem.home should expand tilde paths
453+
project_with_tilde = ProjectItem(
454+
id=1,
455+
name="Test BiSync", # Name differs from path structure
456+
description="Test",
457+
path="~/Documents/Test BiSync", # Path with tilde
458+
is_active=True,
459+
is_default=False,
460+
)
461+
462+
# Before fix: Path("Test BiSync") - wrong!
463+
# After fix: Path("~/Documents/Test BiSync").expanduser() - correct!
464+
home_path = project_with_tilde.home
465+
466+
# Verify it's a Path object
467+
assert isinstance(home_path, Path)
468+
469+
# Verify tilde was expanded (won't contain ~)
470+
assert "~" not in str(home_path)
471+
472+
# Verify it ends with the expected structure (use Path.parts for cross-platform)
473+
assert home_path.parts[-2:] == ("Documents", "Test BiSync")
474+
475+
# Also test that write_note works with regular project
476+
async with Client(mcp_server) as client:
477+
result = await client.call_tool(
478+
"write_note",
479+
{
480+
"project": test_project.name,
481+
"title": "Validation Test",
482+
"folder": "documents",
483+
"content": "Testing path validation",
484+
"tags": "test",
485+
},
486+
)
487+
488+
response_text = result.content[0].text # pyright: ignore [reportAttributeAccessIssue]
489+
490+
# Should successfully create without path validation errors
491+
assert "# Created note" in response_text
492+
assert "not allowed" not in response_text

tests/services/test_project_service.py

Lines changed: 76 additions & 66 deletions

0 commit comments

Comments
 (0)