Summary
On Windows, _cleanup_windows_log_files (src/basic_memory/utils.py:490) can raise FileNotFoundError and kill the process during logging setup, before anything else runs. When the process is the MCP server, the client sees the stdio connection close a couple of seconds after launch, and the session ends up with zero basic-memory tools registered — silently, because nothing in the client surfaces the traceback.
Still present on main @ 816accaa (checked 2026-08-09), identical to the build I hit it on.
The race
# src/basic_memory/utils.py:492-504
stale_logs = [
path
for path in log_dir.glob("basic-memory-*.log*")
if path.is_file() and path.name != current_log_name
]
...
stale_logs.sort(key=lambda path: path.stat().st_mtime, reverse=True)
for stale_log in stale_logs[WINDOWS_LOG_FILE_RETENTION - 1 :]:
try:
stale_log.unlink()
except OSError:
logger.debug(...)
The listing and the stat() in the sort key are separate steps. Windows log files are per-PID, so every basic-memory launch prunes the same shared directory. If a second process unlinks one of those files between the glob and the stat, the sort key raises and the process dies.
The try/except OSError at line 506 guards the unlink but not the stat — so the deletion is protected against exactly this and the read that precedes it is not.
Multiple concurrent launches are the normal case for the Claude Code plugin: several editor sessions, plus basic-memory hook session-start firing per session, all starting at once.
Traceback
File "src/basic_memory/cli/app.py", line 85, in app_callback
init_cli_logging()
File "src/basic_memory/config.py", line 280, in init_cli_logging
_config_logging.initialize_file_logging(...)
File "src/basic_memory/config_logging.py", line 54, in initialize_file_logging
setup_logging(log_level=log_level, log_to_file=True)
File "src/basic_memory/utils.py", line 451, in setup_logging
_cleanup_windows_log_files(log_path.parent, log_path.name)
File "src/basic_memory/utils.py", line 504, in <lambda>
stale_logs.sort(key=lambda path: path.stat().st_mtime, reverse=True)
File "pathlib/__init__.py", line 654, in stat
return os.stat(self, follow_symlinks=follow_symlinks)
FileNotFoundError: [WinError 2] The system cannot find the file specified:
'<data-dir>\\basic-memory-18592.log'
What the client sees
From the MCP client's log:
Starting connection with timeout of 30000ms
Server stderr: <the traceback above>
Connection failed after 2599ms (-32000): MCP error -32000: Connection closed
Two things make this hard to notice:
- No error reaches the model or the user. The session simply has no
mcp__basic-memory__* tools, while the bundled skills, slash commands and output style all go on instructing against tools that do not exist.
- A health check cannot reproduce it.
claude mcp list launches a fresh server, which succeeds, so it reports the server as connected. Diagnosing it requires reading the client's per-session MCP log.
Frequency
Rare but real: 1 failure in 111 connection attempts across my session logs, with 8 stale basic-memory-<pid>.log files in the data directory at the time.
Suggested fix
Make the sort key tolerate a file that has already gone, e.g.
def _mtime(path: Path) -> float:
try:
return path.stat().st_mtime
except OSError:
return -1.0 # vanished under us — sort last, unlink() below is already guarded
stale_logs.sort(key=_mtime, reverse=True)
Since the cleanup is best-effort housekeeping, wrapping the whole call in setup_logging so it can never take down the process would also be reasonable — a failure to prune old logs should not stop the server from starting.
Environment
- basic-memory installed from git
main; MCP server reports FastMCP 3.4.5
- Windows 11, Python 3.14.3
- Client: Claude Code, stdio transport
Summary
On Windows,
_cleanup_windows_log_files(src/basic_memory/utils.py:490) can raiseFileNotFoundErrorand kill the process during logging setup, before anything else runs. When the process is the MCP server, the client sees the stdio connection close a couple of seconds after launch, and the session ends up with zero basic-memory tools registered — silently, because nothing in the client surfaces the traceback.Still present on
main@816accaa(checked 2026-08-09), identical to the build I hit it on.The race
The listing and the
stat()in the sort key are separate steps. Windows log files are per-PID, so every basic-memory launch prunes the same shared directory. If a second process unlinks one of those files between thegloband thestat, the sort key raises and the process dies.The
try/except OSErrorat line 506 guards theunlinkbut not thestat— so the deletion is protected against exactly this and the read that precedes it is not.Multiple concurrent launches are the normal case for the Claude Code plugin: several editor sessions, plus
basic-memory hook session-startfiring per session, all starting at once.Traceback
What the client sees
From the MCP client's log:
Two things make this hard to notice:
mcp__basic-memory__*tools, while the bundled skills, slash commands and output style all go on instructing against tools that do not exist.claude mcp listlaunches a fresh server, which succeeds, so it reports the server as connected. Diagnosing it requires reading the client's per-session MCP log.Frequency
Rare but real: 1 failure in 111 connection attempts across my session logs, with 8 stale
basic-memory-<pid>.logfiles in the data directory at the time.Suggested fix
Make the sort key tolerate a file that has already gone, e.g.
Since the cleanup is best-effort housekeeping, wrapping the whole call in
setup_loggingso it can never take down the process would also be reasonable — a failure to prune old logs should not stop the server from starting.Environment
main; MCP server reports FastMCP 3.4.5