Skip to content

Windows: MCP server can die at startup on a log-cleanup race in _cleanup_windows_log_files #1211

Description

@nickdin

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:

  1. 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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions