Summary
A project entry in config.json with an empty path (observed with a cloud-mode project: "mode": "cloud", "path": "") is resolved as Path(""), which is cwd-relative. Any process that consumes that project's home (e.g. the sync/watch started by basic-memory mcp) treats whatever directory the process was launched from as the project root — and BM sync then writes frontmatter into every markdown file it finds there.
Observed behavior
While running the retrieval benchmarks (basic-memory-benchmarks), a warm basic-memory mcp stdio server was spawned with the repo as cwd, on a machine whose config.json contained:
"projects": {
"main": { "path": "", "mode": "cloud", ... }
}
Result: the benchmark repo's own README.md, AGENTS.md, docs/benchmarks.md, and dataset READMEs all got Basic Memory frontmatter injected (title, type: note, permalink: main/...) — i.e. the main project silently adopted the repo as its local root and synced it.
Why it happens
ProjectConfig is built with home=Path(entry.path) (config.py:775, also :713, :1075). Path("") resolves relative to cwd.
- The
ensure_project_paths_exists validator (config.py:779) explicitly skips non-absolute paths ("cloud-only projects whose path is a slug"), so an empty path passes config load without complaint — but downstream consumers of home don't apply the same guard, so the empty path flows through to local sync as cwd.
Repro
- In
config.json, give a project "path": "" (a cloud-mode project in a non-Postgres local install is the natural way this arises).
- From any directory containing markdown files, start
basic-memory mcp and trigger activity.
- Markdown files in that directory acquire BM frontmatter under the project's permalink namespace.
Proposed fix
Two complementary guards:
- Validate at load: reject or warn on project entries whose
path is empty/relative when they would be used as a local filesystem root (the existing validator already distinguishes absolute vs not — extend that distinction to consumers).
- Gate local sync by mode: a
mode: "cloud" project without an initialized local sync path (bisync_initialized: false, local_sync_path: null) should never be locally synced at all, regardless of what path contains.
Happy to take this one — found it while benchmarking, and the benchmarks repo now isolates itself via BASIC_MEMORY_CONFIG_DIR (basic-memory-benchmarks#14), but the underlying footgun lives here.
🤖 Generated with Claude Code
Summary
A project entry in
config.jsonwith an emptypath(observed with a cloud-mode project:"mode": "cloud", "path": "") is resolved asPath(""), which is cwd-relative. Any process that consumes that project'shome(e.g. the sync/watch started bybasic-memory mcp) treats whatever directory the process was launched from as the project root — and BM sync then writes frontmatter into every markdown file it finds there.Observed behavior
While running the retrieval benchmarks (
basic-memory-benchmarks), a warmbasic-memory mcpstdio server was spawned with the repo as cwd, on a machine whoseconfig.jsoncontained:Result: the benchmark repo's own
README.md,AGENTS.md,docs/benchmarks.md, and dataset READMEs all got Basic Memory frontmatter injected (title,type: note,permalink: main/...) — i.e. themainproject silently adopted the repo as its local root and synced it.Why it happens
ProjectConfigis built withhome=Path(entry.path)(config.py:775, also:713,:1075).Path("")resolves relative to cwd.ensure_project_paths_existsvalidator (config.py:779) explicitly skips non-absolute paths ("cloud-only projects whose path is a slug"), so an empty path passes config load without complaint — but downstream consumers ofhomedon't apply the same guard, so the empty path flows through to local sync as cwd.Repro
config.json, give a project"path": ""(a cloud-mode project in a non-Postgres local install is the natural way this arises).basic-memory mcpand trigger activity.Proposed fix
Two complementary guards:
pathis empty/relative when they would be used as a local filesystem root (the existing validator already distinguishes absolute vs not — extend that distinction to consumers).mode: "cloud"project without an initialized local sync path (bisync_initialized: false,local_sync_path: null) should never be locally synced at all, regardless of whatpathcontains.Happy to take this one — found it while benchmarking, and the benchmarks repo now isolates itself via
BASIC_MEMORY_CONFIG_DIR(basic-memory-benchmarks#14), but the underlying footgun lives here.🤖 Generated with Claude Code