Skip to content

Windows: kill_orphaned_index can't reap native index processes (Git Bash) -> unbounded accumulation / OOM #600

Description

@sdkkds

Summary

On Windows (hooks running under Git Bash), kill_orphaned_index in hooks/common.sh cannot reap the memsearch index processes spawned by the Stop hook. It reaps via Linux pgrep/kill, but the index it spawns is a native Windows python.exe (installed via Scoop/pip), which pgrep cannot see. The Stop hook spawns a fresh index every turn, so on Windows they are never reaped and accumulate until the machine runs out of memory.

Relationship to existing issues (not a duplicate)

Environment

  • Windows 11; Claude Code hooks executed under Git Bash (MSYS2)
  • memsearch plugin 0.4.11, server mode (Milvus at localhost:19530, WSL2 Docker)
  • embedding: onnx bge-m3 int8
  • memsearch installed as a native Windows app (Scoop Python), so command -v memsearch in Git Bash resolves to …\scoop\shims\memsearch.exe

Observed

After one long session:

  • 10 live python.exe … memsearch.exe index <MEMORY_DIR> --collection ms_… processes, ~4.5 GB combined working set.
  • Oldest was 24.7 hours old — it survived a SessionEnd and a full Claude Code restart, proving nothing reaps it.
  • The resulting memory pressure crashed the browser twice and Claude Code once.

The singleton memsearch watch process is fine (one process); the leak is purely the per-turn Stop-hook index.

Root cause

  • hooks/stop.sh runs on every turn and ends with:
    kill_orphaned_index
    run_memsearch index "$MEMORY_DIR"
  • kill_orphaned_index (hooks/common.sh) reaps with pgrep -f "memsearch index $MEMORY_DIR" + kill. Under Git Bash these only see POSIX-emulated processes, not the native Windows python.exe that actually runs the index. So on a Windows host the reaper is a silent no-op and every turn's index orphans.

Suggested fix

Add a Windows branch to kill_orphaned_index that reaps native processes when powershell.exe is on PATH:

# Windows host: reap native index processes that pgrep/kill (Linux) cannot see
# when the hook runs under Git Bash. Matches `index` only, so the singleton
# `watch` process is left alone.
if command -v powershell.exe >/dev/null 2>&1; then
  powershell.exe -NoProfile -NonInteractive -Command "Get-CimInstance Win32_Process | Where-Object { \$_.Name -like 'python*' -and \$_.CommandLine -match 'memsearch' -and \$_.CommandLine -match 'index' } | ForEach-Object { Stop-Process -Id \$_.ProcessId -Force -ErrorAction SilentlyContinue }" >/dev/null 2>&1 || true
fi

A tighter match on $MEMORY_DIR / collection name would scope it per-project (the broad match above kills any memsearch index, which is acceptable for a single-collection setup but not for multiple concurrent projects).

Same blind spot affects stop_watch's pgrep sweep, though that one matters less since watch is a singleton.

Happy to send a PR if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions