Environment
- memsearch 0.4.7 (Claude Code plugin from
zilliztech/memsearch marketplace); hook scripts verified byte-identical on 0.4.12 / current main
- Claude Code on Windows 11 (Git Bash hooks),
embedding.provider = onnx, Milvus server mode (milvus.uri = http://<host>:19530)
Symptoms
-
~/.memsearch/memory/*.md files accumulate that contain only empty ## Session HH:MM headers — never any ### turn summaries — even though all real work happens in per-project .memsearch/memory/ dirs.
-
Some headers are duplicated within the same minute despite the dedup guard:
## Session 16:05
## Session 16:05
## Session 16:08
## Session 16:08
-
A stray memsearch watch C:/Users/<user>/.memsearch/memory --collection ms_<user>_<hash> process runs alongside the legitimate project watcher, and a junk Milvus collection for the home directory gets created and indexed on the server.
-
Occasional phantom empty ## Session headers also appear in the project memory file between real sessions.
Root cause (three interacting issues)
1. The summarizer child re-fires the plugin's own hooks.
hooks/stop.sh summarizes each turn by spawning claude -p (line ~143). That child is a full Claude Code session and runs the plugin's SessionStart hook again — nothing disables hooks/plugins for it. Every completed turn therefore spawns a headless session that appends a ## Session HH:MM header somewhere.
Reproduction: run stop.sh's exact child command from inside any project repo:
MEMSEARCH_NO_WATCH=1 CLAUDECODE= claude -p --strict-mcp-config --tools "" \
--model haiku --no-session-persistence --no-chrome "Reply with the single word: ok"
→ a phantom ## Session HH:MM is appended to that project's memory file. Adding --settings '{"disableAllHooks": true}' to the same command → no header (verified).
2. The $HOME / CWD fallback in directory resolution.
hooks/common.sh (lines ~24–34) resolves the memory dir as: git root of the hook process's CWD → CLAUDE_PROJECT_DIR → .. Headless children and desktop-app helper sessions often run with a CWD outside any repo and with CLAUDE_PROJECT_DIR unset, so they resolve to ~/.memsearch. session-start.sh then writes the header there and calls start_watch, which is where the stray home-dir watcher and the junk ms_<user>_<hash> Milvus collection come from.
3. The same-minute dedup guard is not atomic.
session-start.sh (lines ~113–115) does grep -qF "## Session $NOW" then appends. Two hook processes starting in the same minute both pass the grep before either writes — check-then-write race → duplicated headers.
Suggested fixes
- stop.sh: add
--settings '{"disableAllHooks": true}' to the claude -p invocation so the summarizer cannot recursively trigger plugin hooks. One line; verified working locally.
- session-start.sh / common.sh: when no project resolves (no git root and no
CLAUDE_PROJECT_DIR), skip the header write and start_watch entirely instead of falling back to $HOME/CWD. A header-only memory file with no possibility of summaries has no value, and the fallback silently creates a per-home watcher + collection.
- session-start.sh: make the header append atomic (e.g.
flock on the memory file) or dedupe after writing.
Happy to open a PR for (1) if useful.
Environment
zilliztech/memsearchmarketplace); hook scripts verified byte-identical on 0.4.12 / currentmainembedding.provider = onnx, Milvus server mode (milvus.uri = http://<host>:19530)Symptoms
~/.memsearch/memory/*.mdfiles accumulate that contain only empty## Session HH:MMheaders — never any###turn summaries — even though all real work happens in per-project.memsearch/memory/dirs.Some headers are duplicated within the same minute despite the dedup guard:
A stray
memsearch watch C:/Users/<user>/.memsearch/memory --collection ms_<user>_<hash>process runs alongside the legitimate project watcher, and a junk Milvus collection for the home directory gets created and indexed on the server.Occasional phantom empty
## Sessionheaders also appear in the project memory file between real sessions.Root cause (three interacting issues)
1. The summarizer child re-fires the plugin's own hooks.
hooks/stop.shsummarizes each turn by spawningclaude -p(line ~143). That child is a full Claude Code session and runs the plugin'sSessionStarthook again — nothing disables hooks/plugins for it. Every completed turn therefore spawns a headless session that appends a## Session HH:MMheader somewhere.Reproduction: run stop.sh's exact child command from inside any project repo:
→ a phantom
## Session HH:MMis appended to that project's memory file. Adding--settings '{"disableAllHooks": true}'to the same command → no header (verified).2. The
$HOME/ CWD fallback in directory resolution.hooks/common.sh(lines ~24–34) resolves the memory dir as: git root of the hook process's CWD →CLAUDE_PROJECT_DIR→.. Headless children and desktop-app helper sessions often run with a CWD outside any repo and withCLAUDE_PROJECT_DIRunset, so they resolve to~/.memsearch.session-start.shthen writes the header there and callsstart_watch, which is where the stray home-dir watcher and the junkms_<user>_<hash>Milvus collection come from.3. The same-minute dedup guard is not atomic.
session-start.sh(lines ~113–115) doesgrep -qF "## Session $NOW"then appends. Two hook processes starting in the same minute both pass the grep before either writes — check-then-write race → duplicated headers.Suggested fixes
--settings '{"disableAllHooks": true}'to theclaude -pinvocation so the summarizer cannot recursively trigger plugin hooks. One line; verified working locally.CLAUDE_PROJECT_DIR), skip the header write andstart_watchentirely instead of falling back to$HOME/CWD. A header-only memory file with no possibility of summaries has no value, and the fallback silently creates a per-home watcher + collection.flockon the memory file) or dedupe after writing.Happy to open a PR for (1) if useful.