Summary
prompt_submit observations are silently dropped by the 5-minute dedup window because the dedup hash is computed from ool_input, which does not exist in the prompt_submit payload. All prompt_submit observations for a session share the exact same hash, so within a 300s TTL window only the first user prompt is recorded; every subsequent prompt in that window is discarded, regardless of content.
Root cause
src/functions/dedup.ts (DedupMap):
ts computeHash(sessionId, toolName, toolInput) { const raw = \:\:\; return createHash("sha256").update(raw).digest("hex"); }
mem::observe calls it with d["tool_input"]:
ts const d = typeof payload.data === "object" ? payload.data : {}; const toolName = d["tool_name"] || payload.hookType; dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]);
The client hook plugin/scripts/prompt-submit.mjs sends data: { prompt: data.prompt ?? data.userPrompt } — there is no ool_input key. So for every prompt_submit the hash input is sessionId:prompt_submit:"", identical for all prompts of the session. TTL_MS = 300_000 (5 min), so within any 5-minute window only the first prompt is kept.
Observed
Claude Code session dbf404f9-..., 10 user prompts over 26 minutes. Only 4 prompt_submit observations were stored (timestamps of stored ones: 16:11:53, 16:18:07, 16:23:23, 16:32:35 — exactly the ones ≥5 min after the previous stored prompt). The other 6 prompts (including substantive follow-ups like "did the hooks fire?" / "is it in the plugin?") were dropped. Verified by replaying the 10 timestamps against the 300s window: 4 kept, 6 deduped.
Impact
- User prompts are silently missing from memory; recall/summaries lose real conversation context.
- Clusters of quick follow-up questions (common in interactive sessions) collapse to a single observation.
- The dedup intended for repeated tool calls (e.g. same bash command) also nukes distinct prompts.
Suggested fix
Use the prompt text as the dedup input for prompt_submit, e.g. in mem::observe:
ts if (payload.hookType === "prompt_submit") { dedupHash = dedupMap.computeHash(payload.sessionId, "prompt_submit", d["prompt"]); } else { dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]); }
This dedupes literally repeated prompts (e.g. retrying "你好") while keeping distinct follow-ups. Optionally scope the TTL for prompts shorter (e.g. 60s) since the attack surface is small.
Summary
prompt_submit observations are silently dropped by the 5-minute dedup window because the dedup hash is computed from ool_input, which does not exist in the prompt_submit payload. All prompt_submit observations for a session share the exact same hash, so within a 300s TTL window only the first user prompt is recorded; every subsequent prompt in that window is discarded, regardless of content.
Root cause
src/functions/dedup.ts (DedupMap):
ts computeHash(sessionId, toolName, toolInput) { const raw = \:\:\; return createHash("sha256").update(raw).digest("hex"); }mem::observe calls it with d["tool_input"]:
ts const d = typeof payload.data === "object" ? payload.data : {}; const toolName = d["tool_name"] || payload.hookType; dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]);The client hook plugin/scripts/prompt-submit.mjs sends data: { prompt: data.prompt ?? data.userPrompt } — there is no ool_input key. So for every prompt_submit the hash input is
sessionId:prompt_submit:"", identical for all prompts of the session. TTL_MS = 300_000 (5 min), so within any 5-minute window only the first prompt is kept.Observed
Claude Code session dbf404f9-..., 10 user prompts over 26 minutes. Only 4 prompt_submit observations were stored (timestamps of stored ones: 16:11:53, 16:18:07, 16:23:23, 16:32:35 — exactly the ones ≥5 min after the previous stored prompt). The other 6 prompts (including substantive follow-ups like "did the hooks fire?" / "is it in the plugin?") were dropped. Verified by replaying the 10 timestamps against the 300s window: 4 kept, 6 deduped.
Impact
Suggested fix
Use the prompt text as the dedup input for prompt_submit, e.g. in mem::observe:
ts if (payload.hookType === "prompt_submit") { dedupHash = dedupMap.computeHash(payload.sessionId, "prompt_submit", d["prompt"]); } else { dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]); }This dedupes literally repeated prompts (e.g. retrying "你好") while keeping distinct follow-ups. Optionally scope the TTL for prompts shorter (e.g. 60s) since the attack surface is small.