Documentation Index
Fetch the complete documentation index at: https://mintlify.com/thedotmack/claude-mem/llms.txt
Use this file to discover all available pages before exploring further.
Hook scripts reference
Claude Mem registers hooks viaplugin/hooks/hooks.json. At runtime, Claude Code fires lifecycle events and Claude Mem responds by running the appropriate script. This page documents every script, what it does, and the data contracts it expects.
Hooks.json — the complete configuration
The actual file shipped with the plugin:${CLAUDE_PLUGIN_ROOT} is set by Claude Code to the installed plugin’s root directory. The fallback $HOME/.claude/plugins/marketplaces/thedotmack/plugin handles environments where the variable is not set.The 5 lifecycle stages
| Stage | Script | Lifecycle event | Purpose |
|---|---|---|---|
| 0 (pre-hook) | smart-install.js | Before SessionStart context | Cached dependency check |
| 1 | worker-service.cjs start | SessionStart | Start Bun worker service |
| 2 | worker-service.cjs hook … context | SessionStart | Inject prior session context |
| 3 | worker-service.cjs hook … session-init | UserPromptSubmit | Create session, save prompt |
| 4 | worker-service.cjs hook … observation | PostToolUse | Queue tool observation |
| 5 | worker-service.cjs hook … summarize | Stop | Trigger session summary |
| 6 | cleanup-hook.js (inline) | SessionEnd | Mark session complete |
Script 1: smart-install.js
File:scripts/smart-install.js (pre-hook, not a lifecycle hook)
Lifecycle event: Runs via command chaining before context injection in SessionStart
Matcher: startup|clear|compact
Timeout: 300 seconds
What it does
Checks whether Node.js dependencies are up to date and installs them only when necessary. Without this, every session startup would runnpm install (2–5 seconds).
Decision logic:
- Does
node_modulesexist? - Does
.install-versionmatch the currentpackage.jsonversion? - Is
better-sqlite3present? (Legacy check — bun:sqlite requires no installation)
npm install and write the new version to .install-version.
package.json, .install-version marker file
What it writes: .install-version marker file
Exit codes:
0— Check complete (installed or skipped)1— Installation error (shown to user on stderr)
Script 2: worker-service.cjs start
File:plugin/scripts/worker-service.cjs
Command argument: start
Lifecycle event: SessionStart (second hook in sequence)
Matcher: startup|clear|compact
Timeout: 60 seconds
What it does
Ensures the Bun-managed worker process is running. Uses a three-layer health check:- Does
~/.claude-mem/.worker.pidexist? - Is the process with that PID alive? (
kill(pid, 0)) - Does
GET http://127.0.0.1:37777/healthreturn OK?
~/.claude-mem/.worker.pid, ~/.claude-mem/.worker.port
What it writes: ~/.claude-mem/.worker.pid, ~/.claude-mem/.worker.port
Exit codes:
0— Worker confirmed running0— Worker started successfully (or startup errors are swallowed to prevent session breakage)
Script 3: context-hook.js (worker-service hook … context)
File:plugin/scripts/context-hook.js / worker-service.cjs hook claude-code context
Lifecycle event: SessionStart (third hook in sequence)
Source: src/hooks/context-hook.ts
Matcher: startup|clear|compact
Timeout: 60 seconds
What it does
Fetches context from previous sessions and injects it silently into Claude’s initial system prompt. Processing steps:- Wait for worker to be healthy (health check with retry, max 10s)
- Extract project name from
cwd:path.basename(cwd) - Call
GET http://127.0.0.1:37777/api/context/inject?project={project} - Return the formatted markdown as
hookSpecificOutput.additionalContext
Stdin
Stdout
/api/context/inject) → SQLite session_summaries and observations tables
What it writes: Nothing (read-only)
Configuration: CLAUDE_MEM_CONTEXT_OBSERVATIONS env var controls the number of observations shown (default: 50)
Exit codes:
0— Context injected (or gracefully empty if worker unavailable)
As of Claude Code 2.1.0, context is injected silently via
additionalContext. It is not shown to the user as a message.Script 4: new-hook.js (worker-service hook … session-init)
File:plugin/scripts/new-hook.js / worker-service.cjs hook claude-code session-init
Lifecycle event: UserPromptSubmit
Source: src/hooks/new-hook.ts
Matcher: None (runs for every prompt)
Timeout: 60 seconds
What it does
Creates or retrieves the session record for this conversation and saves the user’s prompt for full-text search. Processing steps:- Extract project name from
cwd - Strip privacy tags (
<private>...</private>,<claude-mem-context>...</claude-mem-context>) from the prompt - If prompt is entirely private after stripping, skip saving
INSERT OR IGNORE INTO sdk_sessions— idempotent; returns existing row if session already exists (continuation prompts)- Increment
prompt_counteron the session row INSERT INTO user_promptswith the cleaned prompt text and prompt number- POST
http://127.0.0.1:37777/sessions/{sessionDbId}/init(fire-and-forget, 2s timeout)
session_id always maps to the same database row. This handles continuation prompts correctly.
Stdin
Stdout
sdk_sessions table
What it writes: SQLite sdk_sessions, user_prompts tables; HTTP POST to worker
Exit codes:
0— Session initialized (or skipped for private prompts)
Script 5: save-hook.js (worker-service hook … observation)
File:plugin/scripts/save-hook.js / worker-service.cjs hook claude-code observation
Lifecycle event: PostToolUse
Source: src/hooks/save-hook.ts
Matcher: * (all tools)
Timeout: 120 seconds
What it does
Captures tool execution data and sends it to the worker for asynchronous AI compression. The hook itself returns in ~8ms; the AI compression runs in the background over 1–30 seconds. Processing steps:- Check the skip list — discard low-value tools immediately:
- Ensure worker is running (health check)
- Strip privacy tags from
tool_inputandtool_response - HTTP POST to worker (fire-and-forget, 2s timeout):
Stdin
Stdout
createSDKSession(claudeSessionId, '', '')→ returnssessionDbId- Checks privacy (skips observation if user prompt was entirely private)
- Strips memory tags from
tool_inputandtool_response(defense-in-depth) - Queues observation for SDK agent
- SDK agent calls Claude to compress into structured XML
- Stores compressed observation in database, syncs to Chroma vector DB
0— Observation queued (or skipped for low-value/private tools)
Script 6: summary-hook.js (worker-service hook … summarize)
File:plugin/scripts/summary-hook.js / worker-service.cjs hook claude-code summarize
Lifecycle event: Stop
Source: src/hooks/summary-hook.ts
Matcher: None (runs on every stop)
Timeout: 120 seconds
What it does
Triggers asynchronous session summary generation when the user stops asking questions. The hook returns immediately; the actual summarization runs in the worker. Processing steps:- Read the transcript JSONL file to extract:
- Last user message (type:
"user") - Last assistant message (type:
"assistant", filtering out<system-reminder>tags)
- Last user message (type:
- Ensure worker is running
- POST to worker (fire-and-forget, 2s timeout):
- Signal the worker to stop the processing spinner:
Stdin
Stdout
session_summaries table)
Async worker processing:
- Looks up
sessionDbIdfromclaudeSessionId - Queues summarization for SDK agent
- SDK agent calls Claude with structured prompt
- Parses XML response with fields:
request,investigated,learned,completed,next_steps - Stores in
session_summariestable, syncs to Chroma
0— Summary queued
Script 7: cleanup-hook.js (inline SessionEnd)
File: Inlinenode -e "..." in hooks.json
Lifecycle event: SessionEnd
Source: src/hooks/cleanup-hook.ts
Matcher: None (runs on all session ends)
Timeout: 5 seconds
What it does
Marks the session as completed so the worker can update its state and broadcast to SSE clients (viewer UI). The hook is intentionally minimal — it is inlined directly inhooks.json to avoid a process startup penalty for a short-lived operation.
Processing steps:
- Parse
sessionIdfrom stdin JSON - POST to worker (fire-and-forget, 3s timeout):
- Exit
0regardless of outcome (HTTP errors are swallowed)
Stdin
Stdout
None (HTTP fire-and-forget). What it reads: Stdin from Claude Code What it writes: HTTP POST to worker (worker updates SQLite, broadcasts SSE event) Async worker processing:- Looks up
sessionDbId UPDATE sdk_sessions SET status = 'completed', completed_at = NOW()- Broadcasts session completion event to SSE clients (updates viewer UI)
0— Always (failure to mark complete is non-fatal)
Session state machine
All seven scripts operate on the same session state:session_idnever changes within a conversationsessionDbIdis the integer primary key — all operations use itpromptNumberincrements with each UserPromptSubmit- State transitions are non-blocking — hooks fire and return
Database schema
The session-centric schema that all hook scripts write to:Privacy and tag stripping
Bothnew-hook.js and save-hook.js strip special tags before data is saved:
stripMemoryTagsFromPrompt() and stripMemoryTagsFromJson() — source: src/utils/tag-stripping.ts.
Common pitfalls
| Problem | Root cause | Solution |
|---|---|---|
| Session ID mismatch | Different session_id used across hooks | Always use ID from hook stdin — never generate your own |
| Duplicate sessions | Creating new session instead of reusing | Use INSERT OR IGNORE with claude_session_id as unique key |
| Blocking IDE | Hook waits for full AI response | Use fire-and-forget with 2s HTTP timeout |
| Memory tags in DB | Tag stripping skipped | Strip at hook layer before HTTP send |
| Worker not found | Health check returns too fast | Retry loop with exponential backoff (max 10s) |
| Context not injecting | npm install logs in stdout | Fixed in v4.3.1 — use --loglevel=silent flag |