How Claude-Mem Uses Hooks: A Lifecycle-Driven Architecture
Core Principle
Observe the main Claude Code session from the outside, process observations in the background, inject context at the right time.The Big Picture
Claude-Mem is fundamentally a hook-driven system. Every piece of functionality happens in response to lifecycle events:As of Claude Code 2.1.0 (ultrathink update), SessionStart hooks no longer display user-visible messages. Context is silently injected via
hookSpecificOutput.additionalContext.Why Hooks?
The Non-Invasive Requirement
Claude-Mem had several architectural constraints:- Can’t modify Claude Code: It’s a closed-source binary
- Must be fast: Can’t slow down the main session
- Must be reliable: Can’t break Claude Code if it fails
- Must be portable: Works on any project without configuration
The Hook System Advantage
Claude Code’s hook system provides exactly what we need:Lifecycle Events
SessionStart, UserPromptSubmit, PreToolUse (Read), PostToolUse, Stop, SessionEnd
Non-Blocking
Hooks run in parallel, don’t wait for completion
Context Injection
SessionStart and UserPromptSubmit can add context
Tool Observation
PostToolUse sees all tool inputs and outputs
The Hook Scripts
Claude-Mem uses lifecycle hook scripts across 5 lifecycle events.npx claude-mem install (and npx claude-mem repair) set up the runtime; the Setup hook runs version-check.js, which completes a plugin-marketplace install whose dependencies are missing and records the install marker. SessionStart runs 2 hooks: worker-service start, which is async so session start never waits on it, and the synchronous context hook, which starts the worker itself when needed.
Setup Hook: Version Check
Purpose: Make sure the loaded plugin can start its worker: install the runtime dependencies the plugin marketplace left out, and record the.install-version marker.
Note: npx claude-mem install and npx claude-mem repair install Bun, uv and the plugin dependencies up front. The plugin marketplace (including claude plugin update) only extracts files, so the Setup hook completes a missing dependency tree itself.
When: Claude Code Setup phase, before every session.
What it does:
- Checks that every dependency declared in the plugin’s
package.json(plus thezodsubpaths the worker loads) is present in itsnode_modules, not just that the folder exists. - If any are missing, runs
bun install --productionin the plugin root (up to 120s), then checks again. A partial tree is kept, never deleted. - If dependencies are still missing, prints
claude-mem: plugin dependencies missing (…) - run: npx claude-mem@latest install, naming the modules (stderr; Codex receives it as SessionStart context). - If the closure is complete and
.install-versionis missing, unreadable or from another version, writes the marker itself ({version, installedAt, bun}) instead of sending a working install to the installer. - Always exits 0 — never blocks a session.
- ✅ Fast when the install is complete: a declared-dependency check, no install
- ✅ Self-heals marketplace installs: installs missing dependencies and writes the marker, so a working install never sees a “run the installer” message
- ✅ Names the missing modules when an install cannot be completed
- ✅ Always exit 0 — never blocks a session
plugin/scripts/version-check.js. npx claude-mem install / npx claude-mem repair do the same work ahead of time — they install Bun + uv globally, run bun install in the plugin cache, and write the .install-version marker — behind a visible clack spinner.
Hook 1: SessionStart - Context Injection
Purpose: Inject relevant context from previous sessions When: Claude Code starts (runs alongside the async worker-start SessionStart entry) What it does:- Extracts project name from current working directory
- Queries SQLite for recent session summaries (last 10)
- Queries SQLite for recent observations (configurable, default 50)
- Formats as progressive disclosure index
- Outputs to stdout (automatically injected into context)
- ✅ Runs on startup, clear, and compact
- ✅ 300-second timeout (allows for npm install if needed)
- ✅ Progressive disclosure format (index, not full details)
- ✅ Configurable observation count via
CLAUDE_MEM_CONTEXT_OBSERVATIONS
src/hooks/context-hook.ts → plugin/scripts/context-hook.js
Hook 2: UserPromptSubmit (New Session Hook)
Purpose: Initialize session tracking when user submits a prompt When: Before Claude processes the user’s message What it does:- Reads user prompt and session ID from stdin
- Creates new session record in SQLite
- Saves raw user prompt for full-text search (v4.2.0+)
- Starts Bun worker service if not running
- Returns immediately (non-blocking)
- ✅ No matcher (runs for all prompts)
- ✅ Creates session record immediately
- ✅ Stores raw prompts for search (privacy note: local SQLite only)
- ✅ Auto-starts worker service
- ✅ Suppresses output (
suppressOutput: true)
src/hooks/new-hook.ts → plugin/scripts/new-hook.js
Hook 3: PostToolUse (Save Observation Hook)
Purpose: Capture tool execution observations for later processing When: Immediately after any tool completes successfully What it does:- Receives tool name, input, output from stdin
- Finds active session for current project
- Enqueues observation in observation_queue table
- Returns immediately (processing happens in worker)
- ✅ Matcher:
*(captures all tools) - ✅ Non-blocking (just enqueues, doesn’t process)
- ✅ Worker processes observations asynchronously
- ✅ Parallel execution safe (each hook gets own stdin)
src/hooks/save-hook.ts → plugin/scripts/save-hook.js
Hook 4: Stop Hook (Summary Generation)
Purpose: Generate AI-powered session summaries during the session When: When Claude stops (triggered by Stop lifecycle event) What it does:- Gathers session observations from database
- Sends to Claude Agent SDK for summarization
- Processes response and extracts structured summary
- Stores in session_summaries table
- ✅ Triggered by Stop lifecycle event
- ✅ Multiple summaries per session (v4.2.0+)
- ✅ Summaries are checkpoints, not endings
- ✅ Uses Claude Agent SDK for AI compression
src/hooks/summary-hook.ts → plugin/scripts/summary-hook.js
Hook 5: SessionEnd (Cleanup Hook)
Purpose: Mark sessions as completed when they end When: Claude Code session ends (not on/clear)
What it does:
- Marks session as completed in database
- Allows worker to finish processing
- Performs graceful cleanup
- ✅ Graceful completion (v4.1.0+)
- ✅ No longer sends DELETE to workers
- ✅ Skips cleanup on
/clearcommands - ✅ Preserves ongoing sessions
- Interrupted summary generation
- Lost pending observations
- Race conditions
- Worker finishes important operations
- Summaries complete successfully
- Clean state transitions
src/hooks/cleanup-hook.ts → plugin/scripts/cleanup-hook.js
Hook Execution Flow
Session Lifecycle
Hook Timing
As of Claude Code 2.1.0 (ultrathink update), SessionStart hooks no longer display user-visible messages. Context is silently injected via
hookSpecificOutput.additionalContext.The Worker Service Architecture
Why a Background Worker?
Problem: Hooks must be fast (< 1 second) Reality: AI compression takes 5-30 seconds per observation Solution: Hooks enqueue observations, worker processes asyncBun Process Management
Technology: Bun (JavaScript runtime and process manager) Why Bun:- Auto-restart on failure
- Fast startup and low memory footprint
- Built-in TypeScript support
- Cross-platform (works on macOS, Linux, Windows)
- No separate process manager needed
Worker HTTP API
Technology: Express.js REST API on the worker’s per-user port (default37700 + (uid % 100), override via CLAUDE_MEM_WORKER_PORT)
Endpoints:
Why HTTP API?
- Language-agnostic (hooks can be any language)
- Easy debugging (curl commands)
- Standard error handling
- Proper async handling
Design Patterns
Pattern 1: Fire-and-Forget Hooks
Principle: Hooks should return immediately, not wait for completionPattern 2: Queue-Based Processing
Principle: Decouple capture from processing- Parallel hook execution safe
- Worker failure doesn’t affect hooks
- Retry logic centralized
- Backpressure handling
Pattern 3: Graceful Degradation
Principle: Memory system failure shouldn’t break Claude Code- Database locked → Skip observation, log error
- Worker crashed → Auto-restart via Bun
- Network issue → Retry with exponential backoff
- Disk full → Warn user, disable memory
Pattern 4: Progressive Enhancement
Principle: Core functionality works without memory, memory enhances itHook Debugging
Debug Mode
Enable detailed hook execution logs:Common Issues
Hook not executing
Hook not executing
Symptoms: Hook command never runsDebugging:
- Check
/hooksmenu - is hook registered? - Verify matcher pattern (case-sensitive!)
- Test command manually:
echo '{}' | node save-hook.js - Check file permissions (executable?)
Hook times out
Hook times out
Symptoms: Hook execution exceeds timeoutDebugging:
- Check timeout setting (default 60s)
- Identify slow operation (database? network?)
- Move slow operation to worker
- Increase timeout if necessary
Context not injecting
Context not injecting
Symptoms: SessionStart hook runs but context missingDebugging:
- Check stdout (must be valid JSON or plain text)
- Verify no stderr output (pollutes JSON)
- Check exit code (must be 0)
- Look for npm install output (v4.3.1 fix)
Observations not captured
Observations not captured
Symptoms: PostToolUse hook runs but observations missingDebugging:
- Check database:
sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM observation_queue" - Verify session exists:
SELECT * FROM sdk_sessions - Check worker status:
npm run worker:status - View worker logs:
npm run worker:logs
Testing Hooks Manually
Performance Considerations
Hook Execution Time
Target: < 100ms per hook Actual measurements:
When the Setup hook is fast, and when it is not:
- With a complete dependency tree it only checks that the declared modules resolve, and rewrites the
.install-versionmarker if it is missing or from another version — no install. - Only an incomplete tree — a plugin-marketplace install or
claude plugin updatewhose dependencies were never installed — runsbun install(up to 120s, inside the Setup hook’s 300s timeout). Setup runs once per Claude Code launch, so this never lands on the per-prompt path. npx claude-mem install/npx claude-mem repairdo the heavy lifting (Bun + uv,bun installinside the plugin cache) up front with a visible clack spinner, so installs made that way never pay it at session start.
Database Performance
Schema optimizations:- Indexes on
project,created_at_epoch,claude_session_id - FTS5 virtual tables for full-text search
- WAL mode for concurrent reads/writes
Worker Throughput
Bottleneck: Claude API latency (5-30s per observation) Mitigation:- Process observations sequentially (simpler, more predictable)
- Skip low-value observations (TodoWrite, ListMcpResourcesTool)
- Batch summaries (generate every N observations, not every observation)
- Parallel processing (multiple workers)
- Smart batching (combine related observations)
- Lazy summarization (summarize only when needed)
Security Considerations
Hook Command Safety
Risk: Hooks execute arbitrary commands with user permissions Mitigations:- Frozen at startup: Hook configuration captured at start, changes require review
- User review required:
/hooksmenu shows changes, requires approval - Plugin isolation:
${CLAUDE_PLUGIN_ROOT}prevents path traversal - Input validation: Hooks validate stdin schema before processing
Data Privacy
What gets stored:- User prompts (raw text) - v4.2.0+
- Tool inputs and outputs
- File paths read/modified
- Session summaries
- All data stored locally in
~/.claude-mem/claude-mem.db - No cloud uploads (API calls only for AI compression). Local installs contact cmem.ai once, at signup, to create the sign-in link, with a numbers-only usage summary (observation counts and token totals, no prompts, paths, or project names); nothing else is sent to cmem.ai.
- SQLite file permissions: user-only read/write
- Product telemetry is opt-out and never carries prompts, code, or paths — see Telemetry
API Key Protection
Configuration:- Anthropic API key in
~/.anthropic/api_keyorANTHROPIC_API_KEYenv var - Worker inherits environment from Claude Code
- Never logged or stored in database
Key Takeaways
- Hooks are interfaces: They define clean boundaries between systems
- Non-blocking is critical: Hooks must return fast, workers do the heavy lifting
- Graceful degradation: Memory system can fail without breaking Claude Code
- Queue-based decoupling: Capture and processing happen independently
- Progressive disclosure: Context injection uses index-first approach
- Lifecycle alignment: Each hook has a clear, single purpose
Further Reading
- Claude Code Hooks Reference - Official documentation
- Progressive Disclosure - Context priming philosophy
- Architecture Evolution - v3 to v4 journey
- Worker Service Design - Background processing details
The hook-driven architecture enables Claude-Mem to be both powerful and invisible. Users never notice the memory system working - it just makes Claude smarter over time.

