Summary
On the Claude Code platform, hooks/session-start.sh appends a ## Session HH:MM heading to the project shadow memory file (<gitroot>/.memsearch/memory/<date>.md) on every session start, unconditionally. The matching content is only written later by hooks/stop.sh, and only when the turn has real content. So any session that starts and ends without captured content leaves a permanent empty heading.
On a setup that opens many short sessions per day (git worktrees, headless claude -p, scheduled jobs, quick interactive checks), these empty headings accumulate fast and degrade two things:
- The SessionStart "# Recent Memory" cold-start injection. It reads the first ~40 heading/bullet lines of the two newest daily files. When a file opens with dozens of empty
## Session headings, the injection is almost all blank stubs and shows little or no real recent context. To the user this looks like the agent has "lost its memory" at session start.
- The search index. The empty headings are embedded as near-empty chunks. In Lite mode the per-turn
memsearch index "$MEMORY_DIR" in stop.sh keeps re-embedding the shadow dir including these stubs, so they crowd out real content in recall results.
Environment
- memsearch
0.4.7 (plugin), Claude Code, macOS, ONNX/local embedding, Milvus Lite.
Where it happens (0.4.7)
hooks/session-start.sh (~lines 113-115) writes the heading unconditionally:
if [ ! -f "$MEMORY_FILE" ] || ! grep -qF "## Session $NOW" "$MEMORY_FILE"; then
echo -e "\n## Session $NOW\n" >> "$MEMORY_FILE"
fi
hooks/stop.sh writes the ### HH:MM content only when the transcript has >= 3 lines (~lines 48-53), plugins.<platform>.summarize.enabled is not false (~lines 57-61), and the parsed turn is non-empty (~lines 154-167). The content is conditional; the heading is not. Mismatch leaves empty headings.
hooks/session-start.sh (~lines 161-176) builds the injection from grep -E '^(#{2,4} |- )' "$f" | head -40 over the two newest files, so leading empty headings crowd out real content.
Real-world impact (one repo)
2026-06-22 shadow file: 222 ## Session headings, 68 empty.
2026-06-23: 134 headings, 36 empty.
- Across 10 daily files: ~193 empty headings out of ~694 total (~28%).
- Net effect: the cold-start injection was dominated by empty stubs in its first 40 lines, and semantic search surfaced shadow stubs over real notes.
No config workaround on Claude Code
The only related config is plugins.claude-code.summarize.enabled=false, which disables content capture entirely but leaves the empty headings and the injection in place, so it makes the symptom worse, not better. The autoRecall / allowPromptInjection toggles exist only for the OpenClaw platform, not Claude Code. There is no documented option to suppress the empty-heading write or the cold-start injection on Claude Code.
Proposed fixes (any one resolves it)
- Preferred - write the heading lazily. Have
stop.sh write the ## Session HH:MM anchor on the first real summary for a session (keyed by session id), and drop the unconditional write in session-start.sh. Empty headings then cannot exist.
- Cheaper - self-heal on start. Before appending a new heading in
session-start.sh, drop any immediately preceding empty ## Session heading (a heading with no non-blank body before the next heading or EOF).
- Most conservative - mitigate the injection and add a toggle. Skip empty sessions when building the "# Recent Memory" block (only include a
## Session heading that has body content), and/or add plugins.claude-code.autoRecall (default true) plus a session_heading toggle so Claude Code users can opt out, matching the OpenClaw toggles.
Fix 1 or 2 is the root fix; fix 3 mitigates the injection side.
I am happy to send a PR for option 1 or 2 if that is welcome.
Summary
On the Claude Code platform,
hooks/session-start.shappends a## Session HH:MMheading to the project shadow memory file (<gitroot>/.memsearch/memory/<date>.md) on every session start, unconditionally. The matching content is only written later byhooks/stop.sh, and only when the turn has real content. So any session that starts and ends without captured content leaves a permanent empty heading.On a setup that opens many short sessions per day (git worktrees, headless
claude -p, scheduled jobs, quick interactive checks), these empty headings accumulate fast and degrade two things:## Sessionheadings, the injection is almost all blank stubs and shows little or no real recent context. To the user this looks like the agent has "lost its memory" at session start.memsearch index "$MEMORY_DIR"instop.shkeeps re-embedding the shadow dir including these stubs, so they crowd out real content in recall results.Environment
0.4.7(plugin), Claude Code, macOS, ONNX/local embedding, Milvus Lite.Where it happens (0.4.7)
hooks/session-start.sh(~lines 113-115) writes the heading unconditionally:hooks/stop.shwrites the### HH:MMcontent only when the transcript has >= 3 lines (~lines 48-53),plugins.<platform>.summarize.enabledis notfalse(~lines 57-61), and the parsed turn is non-empty (~lines 154-167). The content is conditional; the heading is not. Mismatch leaves empty headings.hooks/session-start.sh(~lines 161-176) builds the injection fromgrep -E '^(#{2,4} |- )' "$f" | head -40over the two newest files, so leading empty headings crowd out real content.Real-world impact (one repo)
2026-06-22shadow file: 222## Sessionheadings, 68 empty.2026-06-23: 134 headings, 36 empty.No config workaround on Claude Code
The only related config is
plugins.claude-code.summarize.enabled=false, which disables content capture entirely but leaves the empty headings and the injection in place, so it makes the symptom worse, not better. TheautoRecall/allowPromptInjectiontoggles exist only for the OpenClaw platform, not Claude Code. There is no documented option to suppress the empty-heading write or the cold-start injection on Claude Code.Proposed fixes (any one resolves it)
stop.shwrite the## Session HH:MManchor on the first real summary for a session (keyed by session id), and drop the unconditional write insession-start.sh. Empty headings then cannot exist.session-start.sh, drop any immediately preceding empty## Sessionheading (a heading with no non-blank body before the next heading or EOF).## Sessionheading that has body content), and/or addplugins.claude-code.autoRecall(defaulttrue) plus asession_headingtoggle so Claude Code users can opt out, matching the OpenClaw toggles.Fix 1 or 2 is the root fix; fix 3 mitigates the injection side.
I am happy to send a PR for option 1 or 2 if that is welcome.