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: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, PostToolUse, Stop
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 Seven Hook Scripts
Claude-Mem uses 7 hook scripts across 5 lifecycle events. SessionStart runs 3 hooks in sequence.Hook 1: SessionStart - Smart Install
Purpose: Intelligently manage dependencies and start worker service When: Claude Code starts (startup, clear, or compact) What it does:- Checks if dependencies need installation (version marker)
- Only runs
npm installwhen necessary:- First-time installation
- Version changed in package.json
- Critical dependency missing (better-sqlite3)
- Provides Windows-specific error messages
- Starts PM2 worker service
- ✅ Version caching (
.install-versionfile) - ✅ Fast when already installed (~10ms vs 2-5 seconds)
- ✅ Cross-platform compatible
- ✅ Helpful Windows error messages for build tools
scripts/smart-install.js
Hook 2: SessionStart - Context Injection
Purpose: Inject relevant context from previous sessions When: Claude Code starts (runs after smart-install) 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 3: SessionStart - User Message
Purpose: Display helpful user messages during first-time setup When: Claude Code starts (runs after context-hook) What it does:- Checks if dependencies are installed
- Shows first-time setup message if needed
- Displays formatted context information with colors
- Shows link to viewer UI (http://localhost:37777)
- Exits with code 3 (informational, not error)
- ✅ User-friendly first-time experience
- ✅ Visual context display
- ✅ Links to viewer UI
- ✅ Non-intrusive (exit code 3)
plugin/scripts/user-message-hook.js (minified)
Hook 4: 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 PM2 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 5: 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 6: Summary Hook (Mid-Session Checkpoint)
Purpose: Generate AI-powered session summaries during the session When: Triggered programmatically by the worker service 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 worker, not by Claude Code lifecycle
- ✅ 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 7: 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
| Event | Timing | Blocking | Timeout | Output Handling |
|---|---|---|---|---|
| SessionStart (smart-install) | Before session | No | 300s | stderr (info) |
| SessionStart (context) | Before session | No | 300s | stdout → context |
| SessionStart (user-message) | Before session | No | 10s | stderr (info) |
| UserPromptSubmit | Before processing | No | 120s | stdout → context |
| PostToolUse | After tool | No | 120s | Transcript only |
| Summary | Worker triggered | No | 120s | Database |
| SessionEnd | On exit | No | 120s | Log only |
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 asyncPM2 Process Management
Technology: PM2 (process manager for Node.js) Why PM2:- Auto-restart on failure
- Log management
- Process monitoring
- Cross-platform (works on macOS, Linux, Windows)
- No systemd/launchd needed
Worker HTTP API
Technology: Express.js REST API on port 37777 Endpoints:| Endpoint | Method | Purpose |
|---|---|---|
/health | GET | Health check |
/sessions | POST | Create session |
/sessions/:id | GET | Get session status |
/sessions/:id | PATCH | Update session |
/observations | POST | Enqueue observation |
/observations/:id | GET | Get observation |
- 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 PM2
- 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:
pm2 status - View worker logs:
pm2 logs claude-mem-worker
Testing Hooks Manually
Performance Considerations
Hook Execution Time
Target: < 100ms per hook Actual measurements:| Hook | Average | p95 | p99 |
|---|---|---|---|
| SessionStart (smart-install, cached) | 10ms | 20ms | 40ms |
| SessionStart (smart-install, first run) | 2500ms | 5000ms | 8000ms |
| SessionStart (context) | 45ms | 120ms | 250ms |
| SessionStart (user-message) | 5ms | 10ms | 15ms |
| UserPromptSubmit | 12ms | 25ms | 50ms |
| PostToolUse | 8ms | 15ms | 30ms |
| SessionEnd | 5ms | 10ms | 20ms |
- First-time: Full npm install (2-5 seconds)
- Cached: Version check only (~10ms)
- Version change: Full npm install + PM2 restart
- Version caching with
.install-versionmarker - Only install on version change or missing deps
- Windows-specific error messages with build tool help
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)
- SQLite file permissions: user-only read/write
- No analytics or 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.

