Skip to main content

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:
┌─────────────────────────────────────────────────────────┐
│              CLAUDE CODE SESSION                         │
│  (Main session - user interacting with Claude)          │
│                                                          │
│  SessionStart → UserPromptSubmit → Tool Use → Stop      │
│     ↓ ↓ ↓            ↓               ↓          ↓        │
│  [3 Hooks]        [Hook]          [Hook]     [Hook]     │
└─────────────────────────────────────────────────────────┘
    ↓ ↓ ↓             ↓               ↓          ↓
┌─────────────────────────────────────────────────────────┐
│                  CLAUDE-MEM SYSTEM                       │
│                                                          │
│  Smart      Context     User       New        Obs       │
│  Install    Inject      Message    Session    Capture   │
└─────────────────────────────────────────────────────────┘
Key insight: Claude-Mem doesn’t interrupt or modify Claude Code’s behavior. It observes from the outside and provides value through lifecycle hooks.

Why Hooks?

The Non-Invasive Requirement

Claude-Mem had several architectural constraints:
  1. Can’t modify Claude Code: It’s a closed-source binary
  2. Must be fast: Can’t slow down the main session
  3. Must be reliable: Can’t break Claude Code if it fails
  4. Must be portable: Works on any project without configuration
Solution: External command hooks configured via settings.json

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:
  1. Checks if dependencies need installation (version marker)
  2. Only runs npm install when necessary:
    • First-time installation
    • Version changed in package.json
    • Critical dependency missing (better-sqlite3)
  3. Provides Windows-specific error messages
  4. Starts PM2 worker service
Configuration:
{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup|clear|compact",
      "hooks": [{
        "type": "command",
        "command": "node \"${CLAUDE_PLUGIN_ROOT}/../scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
        "timeout": 300
      }]
    }]
  }
}
Key Features:
  • ✅ Version caching (.install-version file)
  • ✅ Fast when already installed (~10ms vs 2-5 seconds)
  • ✅ Cross-platform compatible
  • ✅ Helpful Windows error messages for build tools
v5.0.3 Enhancement: Smart caching eliminates redundant installs Source: 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:
  1. Extracts project name from current working directory
  2. Queries SQLite for recent session summaries (last 10)
  3. Queries SQLite for recent observations (configurable, default 50)
  4. Formats as progressive disclosure index
  5. Outputs to stdout (automatically injected into context)
Key decisions:
  • ✅ 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
Output format:
# [claude-mem] recent context

**Legend:** 🎯 session-request | 🔴 gotcha | 🟡 problem-solution ...

### Oct 26, 2025

**General**
| ID | Time | T | Title | Tokens |
|----|------|---|-------|--------|
| #2586 | 12:58 AM | 🔵 | Context hook file empty | ~51 |

*Use claude-mem MCP search to access full details*
Source: src/hooks/context-hook.tsplugin/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:
  1. Checks if dependencies are installed
  2. Shows first-time setup message if needed
  3. Displays formatted context information with colors
  4. Shows link to viewer UI (http://localhost:37777)
  5. Exits with code 3 (informational, not error)
Configuration:
{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup|clear|compact",
      "hooks": [{
        "type": "command",
        "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js",
        "timeout": 10
      }]
    }]
  }
}
Output Example:
📝 Claude-Mem Context Loaded
   ℹ️  Note: This appears as stderr but is informational only

[Context details with colors...]

📺 Watch live in browser http://localhost:37777/ (New! v5.1)
Key Features:
  • ✅ User-friendly first-time experience
  • ✅ Visual context display
  • ✅ Links to viewer UI
  • ✅ Non-intrusive (exit code 3)
Source: 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:
  1. Reads user prompt and session ID from stdin
  2. Creates new session record in SQLite
  3. Saves raw user prompt for full-text search (v4.2.0+)
  4. Starts PM2 worker service if not running
  5. Returns immediately (non-blocking)
Configuration:
{
  "hooks": {
    "UserPromptSubmit": [{
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js"
      }]
    }]
  }
}
Key decisions:
  • ✅ 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)
Database operations:
INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, ...)
VALUES (?, ?, ?, ...)

INSERT INTO user_prompts (session_id, prompt, prompt_number, ...)
VALUES (?, ?, ?, ...)
Source: src/hooks/new-hook.tsplugin/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:
  1. Receives tool name, input, output from stdin
  2. Finds active session for current project
  3. Enqueues observation in observation_queue table
  4. Returns immediately (processing happens in worker)
Configuration:
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js"
      }]
    }]
  }
}
Key decisions:
  • ✅ Matcher: * (captures all tools)
  • ✅ Non-blocking (just enqueues, doesn’t process)
  • ✅ Worker processes observations asynchronously
  • ✅ Parallel execution safe (each hook gets own stdin)
Database operations:
INSERT INTO observation_queue (session_id, tool_name, tool_input, tool_output, ...)
VALUES (?, ?, ?, ?, ...)
What gets queued:
{
  "session_id": "abc123",
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/path/to/file.ts",
    "old_string": "...",
    "new_string": "..."
  },
  "tool_output": {
    "success": true,
    "linesChanged": 5
  },
  "created_at_epoch": 1698765432
}
Source: src/hooks/save-hook.tsplugin/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:
  1. Gathers session observations from database
  2. Sends to Claude Agent SDK for summarization
  3. Processes response and extracts structured summary
  4. Stores in session_summaries table
Configuration:
{
  "hooks": {
    "Summary": [{
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js"
      }]
    }]
  }
}
Key decisions:
  • ✅ 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
Summary structure:
<summary>
  <request>User's original request</request>
  <investigated>What was examined</investigated>
  <learned>Key discoveries</learned>
  <completed>Work finished</completed>
  <next_steps>Remaining tasks</next_steps>
  <files_read>
    <file>path/to/file1.ts</file>
    <file>path/to/file2.ts</file>
  </files_read>
  <files_modified>
    <file>path/to/file3.ts</file>
  </files_modified>
  <notes>Additional context</notes>
</summary>
Source: src/hooks/summary-hook.tsplugin/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:
  1. Marks session as completed in database
  2. Allows worker to finish processing
  3. Performs graceful cleanup
Configuration:
{
  "hooks": {
    "SessionEnd": [{
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js"
      }]
    }]
  }
}
Key decisions:
  • ✅ Graceful completion (v4.1.0+)
  • ✅ No longer sends DELETE to workers
  • ✅ Skips cleanup on /clear commands
  • ✅ Preserves ongoing sessions
Why graceful cleanup? Old approach (v3):
// ❌ Aggressive cleanup
SessionEndDELETE /worker/sessionWorker stops immediately
Problems:
  • Interrupted summary generation
  • Lost pending observations
  • Race conditions
New approach (v4.1.0+):
// ✅ Graceful completion
SessionEndUPDATE sessions SET completed_at = NOW()
Worker sees completionFinishes processingExits naturally
Benefits:
  • Worker finishes important operations
  • Summaries complete successfully
  • Clean state transitions
Source: src/hooks/cleanup-hook.tsplugin/scripts/cleanup-hook.js

Hook Execution Flow

Session Lifecycle

Hook Timing

EventTimingBlockingTimeoutOutput Handling
SessionStart (smart-install)Before sessionNo300sstderr (info)
SessionStart (context)Before sessionNo300sstdout → context
SessionStart (user-message)Before sessionNo10sstderr (info)
UserPromptSubmitBefore processingNo120sstdout → context
PostToolUseAfter toolNo120sTranscript only
SummaryWorker triggeredNo120sDatabase
SessionEndOn exitNo120sLog 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 async
┌─────────────────────────────────────────────────────────┐
│                   HOOK (Fast)                            │
│  1. Read stdin (< 1ms)                                  │
│  2. Insert into queue (< 10ms)                          │
│  3. Return success (< 20ms total)                       │
└─────────────────────────────────────────────────────────┘
                        ↓ (queue)
┌─────────────────────────────────────────────────────────┐
│                 WORKER (Slow)                            │
│  1. Poll queue every 1s                                 │
│  2. Process observation via Claude SDK (5-30s)          │
│  3. Parse and store results                             │
│  4. Mark observation processed                          │
└─────────────────────────────────────────────────────────┘

PM2 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
Configuration:
// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'claude-mem-worker',
    script: './plugin/scripts/worker-service.cjs',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '500M',
    env: {
      NODE_ENV: 'production',
      CLAUDE_MEM_WORKER_PORT: 37777
    }
  }]
};
Worker lifecycle:
# Started by new-hook (if not running)
pm2 start ecosystem.config.cjs

# Status check
pm2 status claude-mem-worker

# View logs
pm2 logs claude-mem-worker

# Restart
pm2 restart claude-mem-worker

Worker HTTP API

Technology: Express.js REST API on port 37777 Endpoints:
EndpointMethodPurpose
/healthGETHealth check
/sessionsPOSTCreate session
/sessions/:idGETGet session status
/sessions/:idPATCHUpdate session
/observationsPOSTEnqueue observation
/observations/:idGETGet observation
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 completion
// ❌ Bad: Hook waits for processing
export async function saveHook(stdin: HookInput) {
  const observation = parseInput(stdin);
  await processObservation(observation);  // BLOCKS!
  return success();
}

// ✅ Good: Hook enqueues and returns
export async function saveHook(stdin: HookInput) {
  const observation = parseInput(stdin);
  await enqueueObservation(observation);  // Fast
  return success();  // Immediate
}

Pattern 2: Queue-Based Processing

Principle: Decouple capture from processing
Hook (capture) → Queue (buffer) → Worker (process)
Benefits:
  • 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
try {
  await captureObservation();
} catch (error) {
  // Log error, but don't throw
  console.error('Memory capture failed:', error);
  return { continue: true, suppressOutput: true };
}
Failure modes:
  • 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 it
Without memory: Claude Code works normally
With memory:    Claude Code + context from past sessions
Memory broken:  Falls back to working normally

Hook Debugging

Debug Mode

Enable detailed hook execution logs:
claude --debug
Output:
[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Getting matching hook commands for PostToolUse with query: Write
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 hooks for query "Write"
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: ${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js with timeout 60000ms
[DEBUG] Hook command completed with status 0: {"continue":true,"suppressOutput":true}

Common Issues

Symptoms: Hook command never runsDebugging:
  1. Check /hooks menu - is hook registered?
  2. Verify matcher pattern (case-sensitive!)
  3. Test command manually: echo '{}' | node save-hook.js
  4. Check file permissions (executable?)
Symptoms: Hook execution exceeds timeoutDebugging:
  1. Check timeout setting (default 60s)
  2. Identify slow operation (database? network?)
  3. Move slow operation to worker
  4. Increase timeout if necessary
Symptoms: SessionStart hook runs but context missingDebugging:
  1. Check stdout (must be valid JSON or plain text)
  2. Verify no stderr output (pollutes JSON)
  3. Check exit code (must be 0)
  4. Look for npm install output (v4.3.1 fix)
Symptoms: PostToolUse hook runs but observations missingDebugging:
  1. Check database: sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM observation_queue"
  2. Verify session exists: SELECT * FROM sdk_sessions
  3. Check worker status: pm2 status
  4. View worker logs: pm2 logs claude-mem-worker

Testing Hooks Manually

# Test context hook
echo '{
  "session_id": "test123",
  "cwd": "/Users/alex/projects/my-app",
  "hook_event_name": "SessionStart",
  "source": "startup"
}' | node plugin/scripts/context-hook.js

# Test save hook
echo '{
  "session_id": "test123",
  "tool_name": "Edit",
  "tool_input": {"file_path": "test.ts"},
  "tool_output": {"success": true}
}' | node plugin/scripts/save-hook.js

# Test with actual Claude Code
claude --debug
/hooks  # View registered hooks
# Submit prompt and watch debug output

Performance Considerations

Hook Execution Time

Target: < 100ms per hook Actual measurements:
HookAveragep95p99
SessionStart (smart-install, cached)10ms20ms40ms
SessionStart (smart-install, first run)2500ms5000ms8000ms
SessionStart (context)45ms120ms250ms
SessionStart (user-message)5ms10ms15ms
UserPromptSubmit12ms25ms50ms
PostToolUse8ms15ms30ms
SessionEnd5ms10ms20ms
Why smart-install is sometimes slow:
  • First-time: Full npm install (2-5 seconds)
  • Cached: Version check only (~10ms)
  • Version change: Full npm install + PM2 restart
Optimization (v5.0.3):
  • Version caching with .install-version marker
  • 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
Query patterns:
-- Fast: Uses index on (project, created_at_epoch)
SELECT * FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT 10

-- Fast: Uses index on claude_session_id
SELECT * FROM sdk_sessions
WHERE claude_session_id = ?
LIMIT 1

-- Fast: FTS5 full-text search
SELECT * FROM observations_fts
WHERE observations_fts MATCH ?
ORDER BY rank
LIMIT 20

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)
Future optimization:
  • 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:
  1. Frozen at startup: Hook configuration captured at start, changes require review
  2. User review required: /hooks menu shows changes, requires approval
  3. Plugin isolation: ${CLAUDE_PLUGIN_ROOT} prevents path traversal
  4. 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
Privacy guarantees:
  • 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_key or ANTHROPIC_API_KEY env var
  • Worker inherits environment from Claude Code
  • Never logged or stored in database

Key Takeaways

  1. Hooks are interfaces: They define clean boundaries between systems
  2. Non-blocking is critical: Hooks must return fast, workers do the heavy lifting
  3. Graceful degradation: Memory system can fail without breaking Claude Code
  4. Queue-based decoupling: Capture and processing happen independently
  5. Progressive disclosure: Context injection uses index-first approach
  6. Lifecycle alignment: Each hook has a clear, single purpose

Further Reading


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.