Skip to content

Commit 29ebfd7

Browse files
committed
fix(cli): also detect bm/bm.exe entrypoints in mcp pre-flight scan
Codex P1 on #776: the previous matcher required the literal string 'basic-memory' somewhere in argv, so a process started via the supported `bm mcp` alias entrypoint slipped through and the reset proceeded against a live MCP — the exact bug the pre-flight is supposed to block. Extract the matching to _is_basic_memory_mcp() and recognize: - basic-memory / basic_memory tokens (any position) - bm script invocations across path conventions (`bm`, `/usr/local/bin/bm`, `bm.exe`, `~/.venv/Scripts/bm.exe`) using both PurePosixPath and PureWindowsPath so cross-OS test fixtures work regardless of the host platform. Test now covers both the alias forms and the negative cases (`mcp` appearing as a substring or in an unrelated server's argv). Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 9c4b1f7 commit 29ebfd7

2 files changed

Lines changed: 47 additions & 11 deletions

File tree

src/basic_memory/cli/commands/db.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44
from dataclasses import dataclass
5-
from pathlib import Path
5+
from pathlib import Path, PurePosixPath, PureWindowsPath
66

77
import psutil
88
import typer
@@ -23,6 +23,36 @@
2323
console = Console()
2424

2525

26+
def _is_basic_memory_mcp(cmdline: list[str]) -> bool:
27+
"""Heuristic: does this argv represent a `basic-memory mcp` server?
28+
29+
The MCP server can be launched any of:
30+
basic-memory mcp
31+
bm mcp # entrypoint alias from pyproject.toml
32+
python -m basic_memory.cli.main mcp # module form
33+
uv run basic-memory mcp / uv run bm mcp # uv wrappers
34+
/abs/path/to/{bm,basic-memory}[.exe] mcp
35+
36+
A reliable match needs both signals:
37+
1. "mcp" appears as an exact argv token (not "mcp-foo").
38+
2. Some argv token names the basic-memory entrypoint — either by
39+
hyphen/underscore form, or as a `bm` script (covers `/usr/local/bin/bm`,
40+
`bm.exe`, etc. via Path.stem).
41+
"""
42+
if "mcp" not in cmdline:
43+
return False
44+
for arg in cmdline:
45+
if "basic-memory" in arg or "basic_memory" in arg:
46+
return True
47+
# Try both POSIX and Windows path interpretations so a test on
48+
# macOS still recognizes `C:\\...\\bm.exe`, and a real Windows
49+
# run still recognizes `/usr/local/bin/bm`. Path() alone uses
50+
# the host OS, which gives wrong stems for foreign separators.
51+
if PurePosixPath(arg).stem == "bm" or PureWindowsPath(arg).stem == "bm":
52+
return True
53+
return False
54+
55+
2656
def _find_live_mcp_processes() -> list[tuple[int, str]]:
2757
"""Return (pid, joined_cmdline) for live `basic-memory mcp` processes.
2858
@@ -51,11 +81,8 @@ def _find_live_mcp_processes() -> list[tuple[int, str]]:
5181
cmdline = proc.info.get("cmdline") or []
5282
if not cmdline:
5383
continue
54-
# Match the full `basic-memory mcp` invocation, not just any
55-
# process whose argv mentions either word individually.
56-
joined = " ".join(cmdline)
57-
if "basic-memory" in joined and "mcp" in cmdline:
58-
matches.append((pid, joined))
84+
if _is_basic_memory_mcp(cmdline):
85+
matches.append((pid, " ".join(cmdline)))
5986
except (psutil.NoSuchProcess, psutil.AccessDenied):
6087
continue
6188
return matches

tests/cli/test_db_reset_zombie_check.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,28 @@ def test_matches_basic_memory_mcp_invocations(self, monkeypatch):
4646
_patch_iter(
4747
monkeypatch,
4848
[
49+
# Direct `basic-memory mcp`.
4950
_FakeProc(pid=101, cmdline=["/usr/bin/python", "basic-memory", "mcp"]),
50-
_FakeProc(pid=202, cmdline=["bm", "mcp"]), # alt entrypoint, no match
51+
# `bm mcp` alias entrypoint — must also match (#765 P1).
52+
_FakeProc(pid=202, cmdline=["bm", "mcp"]),
53+
# Python module form, underscore name.
5154
_FakeProc(
5255
pid=303,
53-
cmdline=["python", "-m", "basic_memory.cli.main", "basic-memory", "mcp"],
56+
cmdline=["python", "-m", "basic_memory.cli.main", "mcp"],
5457
),
58+
# Absolute path to the bm script.
59+
_FakeProc(pid=404, cmdline=["/usr/local/bin/bm", "mcp"]),
60+
# Windows-style bm.exe.
61+
_FakeProc(pid=505, cmdline=["C:\\Users\\me\\.venv\\Scripts\\bm.exe", "mcp"]),
62+
# Should NOT match — `mcp` is a substring of another arg, not a token.
63+
_FakeProc(pid=606, cmdline=["python", "basic-memory", "mcp-helper"]),
64+
# Should NOT match — has `mcp` but no basic-memory/bm signature.
65+
_FakeProc(pid=707, cmdline=["python", "/some/other/server.py", "mcp"]),
5566
],
5667
)
5768
result = db_cmd._find_live_mcp_processes()
58-
# 101 and 303 match (cmdline contains both "basic-memory" and exact "mcp" arg).
59-
# 202 is intentionally excluded because the joined cmdline has no "basic-memory".
6069
pids = sorted(pid for pid, _ in result)
61-
assert pids == [101, 303]
70+
assert pids == [101, 202, 303, 404, 505]
6271

6372
def test_skips_current_process(self, monkeypatch):
6473
me = os.getpid()

0 commit comments

Comments
 (0)