Bug
The Stop hook in mempal_save_hook.sh (lines 87-104) counts every JSONL entry with role: "user" as a human message. In Claude Code transcripts, tool results (Bash, Read, Grep, subagent dispatches, etc.) are also sent as role: "user" with content blocks of type: "tool_result".
This inflates the exchange count significantly. In one measured session, 425 actual human messages produced a count of 1,221 — a ~2.9x inflation. Tool-heavy and subagent-heavy sessions trigger saves far more often than intended.
Current behavior
if isinstance(msg, dict) and msg.get('role') == 'user':
content = msg.get('content', '')
if isinstance(content, str) and '<command-message>' in content:
continue
count += 1
Only <command-message> strings are filtered. Tool results (where content is a list of {"type": "tool_result", ...} blocks) are counted as human messages.
Expected behavior
Only actual human text input should increment the counter. Tool results should be skipped.
Proposed fix
When content is a list, check whether all blocks are tool_result type — if so, skip:
if isinstance(msg, dict) and msg.get('role') == 'user':
content = msg.get('content', '')
if isinstance(content, str) and '<command-message>' in content:
continue
# Skip tool results — they arrive as role: "user" but aren't human input
if isinstance(content, list) and all(
isinstance(b, dict) and b.get('type') == 'tool_result'
for b in content
):
continue
count += 1
This is safe for non-Claude-Code transcript formats: if another LLM uses role: "tool" for tool results (e.g. OpenAI format), those entries never match role == "user" in the first place, so this filter is a no-op.
Related
Bug
The
Stophook inmempal_save_hook.sh(lines 87-104) counts every JSONL entry withrole: "user"as a human message. In Claude Code transcripts, tool results (Bash,Read,Grep, subagent dispatches, etc.) are also sent asrole: "user"with content blocks oftype: "tool_result".This inflates the exchange count significantly. In one measured session, 425 actual human messages produced a count of 1,221 — a ~2.9x inflation. Tool-heavy and subagent-heavy sessions trigger saves far more often than intended.
Current behavior
Only
<command-message>strings are filtered. Tool results (wherecontentis a list of{"type": "tool_result", ...}blocks) are counted as human messages.Expected behavior
Only actual human text input should increment the counter. Tool results should be skipped.
Proposed fix
When
contentis a list, check whether all blocks aretool_resulttype — if so, skip:This is safe for non-Claude-Code transcript formats: if another LLM uses
role: "tool"for tool results (e.g. OpenAI format), those entries never matchrole == "user"in the first place, so this filter is a no-op.Related
hooks_cli.py, which doesn't exist onmain— the counting logic lives in inline Python withinhooks/mempal_save_hook.sh