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 Six Hook Scripts + Pre-Hook
Claude-Mem uses 6 lifecycle hook scripts across 5 lifecycle events, plus 1 pre-hook script for dependency management. SessionStart runs 2 hooks in sequence (after the pre-hook script).Pre-Hook: Smart Install (Before SessionStart)
Purpose: Intelligently manage dependencies and start worker service Note: This is NOT a lifecycle hook - it’s a pre-hook script executed via command chaining before context-hook runs. 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
- Provides Windows-specific error messages
- Starts Bun 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 1: SessionStart - Context Injection
Purpose: Inject relevant context from previous sessions When: Claude Code starts (runs after smart-install pre-hook) 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: 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 3: 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 4: 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 5: 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 6: 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 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 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 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:| 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 + worker 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.

