📋 Pre-flight Checks
📝 Bug Description
The Claude Code plugin ships a UserPromptSubmit hook (plugin/claude-code/scripts/user-prompt-submit.sh, and the .ps1 variant) whose manifest advertises "passive capture", but in practice the hook never persists the user's prompt.
On the first message of a session it injects the ToolSearch instruction; on subsequent messages it only emits save-nudges (curl GET ...). It reads the hook payload from stdin but never extracts .prompt and never calls POST /prompts.
Net effect: the prompts table stays near-empty while observations keep accumulating, so mem_save's best-effort prompt capture has nothing in SessionActivity to attach. Memories lose their originating-prompt context and recall quality degrades.
Evidence (two independent environments):
- Local machine: 470 observations across 16 sessions, but only 5 prompts — and those 5 came from rare manual
mem_save_prompt MCP calls, not the hook.
- A separate machine syncing to a self-hosted Engram Cloud instance (VPS): same result — no prompts persisted beyond the few manual ones.
Platform-independent — confirmed on main (plugin 0.1.1), both user-prompt-submit.sh and user-prompt-submit.ps1.
🔄 Steps to Reproduce
- Install the Engram plugin in Claude Code and run
engram serve.
- Have a normal multi-message session in any project (let the agent call
mem_save a few times).
- Run
engram stats (or GET /prompts/recent).
- Observe:
Observations grows with each save, but Prompts stays at ~0 — it only ticks up on the rare occasions the agent calls mem_save_prompt itself.
✅ Expected Behavior
The UserPromptSubmit hook should persist each user prompt to Engram via POST /prompts with {session_id, content, project}. The session is already registered by session-start.sh under the same Claude session_id, and validateSessionProject passes when the project matches or is empty — so the call succeeds and mem_save can attach the originating prompt and dedupe via SessionActivity, as the protocol describes.
❌ Actual Behavior
The hook never calls POST /prompts; the prompt read from stdin is discarded. In user-prompt-submit.sh, INPUT=$(cat) is read but only .cwd and .session_id are extracted — never .prompt — and there is no write path to the server.
🔧 Suggested Fix (validated end-to-end against the local server)
Add a prompt-persist step in the hook (after the first-message branch):
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty')
[ -n "$PROMPT" ] && curl -sf -X POST "${ENGRAM_URL}/prompts" --max-time 2 \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg s "$SESSION_ID" --arg p "$PROJECT" --arg c "$PROMPT" \
'{session_id:$s, project:$p, content:$c}')" >/dev/null 2>&1
Plus the Invoke-RestMethod equivalent in user-prompt-submit.ps1.
I verified this exact POST /prompts call works end-to-end: piping a simulated Claude UserPromptSubmit payload through the hook incremented /prompts/recent and stored the prompt with the correct project attribution. As a local workaround I wired an additional UserPromptSubmit hook in settings.json that does this POST, and prompt capture now works — but the fix belongs in the plugin so every user benefits.
🖥️ Environment
- OS: Windows 11 (also reproduced via Engram Cloud on a Linux VPS)
- engram:
1.16.3
- plugin:
0.1.0 (bug also confirmed on main / 0.1.1)
- Agent: Claude Code
📋 Pre-flight Checks
status:approvedbefore a PR can be opened📝 Bug Description
The Claude Code plugin ships a
UserPromptSubmithook (plugin/claude-code/scripts/user-prompt-submit.sh, and the.ps1variant) whose manifest advertises "passive capture", but in practice the hook never persists the user's prompt.On the first message of a session it injects the ToolSearch instruction; on subsequent messages it only emits save-nudges (
curl GET ...). It reads the hook payload from stdin but never extracts.promptand never callsPOST /prompts.Net effect: the
promptstable stays near-empty while observations keep accumulating, somem_save's best-effort prompt capture has nothing inSessionActivityto attach. Memories lose their originating-prompt context and recall quality degrades.Evidence (two independent environments):
mem_save_promptMCP calls, not the hook.Platform-independent — confirmed on
main(plugin0.1.1), bothuser-prompt-submit.shanduser-prompt-submit.ps1.🔄 Steps to Reproduce
engram serve.mem_savea few times).engram stats(orGET /prompts/recent).Observationsgrows with each save, butPromptsstays at ~0 — it only ticks up on the rare occasions the agent callsmem_save_promptitself.✅ Expected Behavior
The
UserPromptSubmithook should persist each user prompt to Engram viaPOST /promptswith{session_id, content, project}. The session is already registered bysession-start.shunder the same Claudesession_id, andvalidateSessionProjectpasses when the project matches or is empty — so the call succeeds andmem_savecan attach the originating prompt and dedupe viaSessionActivity, as the protocol describes.❌ Actual Behavior
The hook never calls
POST /prompts; the prompt read from stdin is discarded. Inuser-prompt-submit.sh,INPUT=$(cat)is read but only.cwdand.session_idare extracted — never.prompt— and there is no write path to the server.🔧 Suggested Fix (validated end-to-end against the local server)
Add a prompt-persist step in the hook (after the first-message branch):
Plus the
Invoke-RestMethodequivalent inuser-prompt-submit.ps1.I verified this exact
POST /promptscall works end-to-end: piping a simulated ClaudeUserPromptSubmitpayload through the hook incremented/prompts/recentand stored the prompt with the correct project attribution. As a local workaround I wired an additionalUserPromptSubmithook insettings.jsonthat does this POST, and prompt capture now works — but the fix belongs in the plugin so every user benefits.🖥️ Environment
1.16.30.1.0(bug also confirmed onmain/0.1.1)