docs/public/architecture/pm2-to-bun-migration.mdx
This document describes the PM2 to Bun migration that occurred in v7.1.0 (December 2025). If you're installing claude-mem for the first time, this migration has already been completed and you can use the current Bun-based system documented in the main guides.
This documentation is preserved for users upgrading from versions older than v7.1.0. </Note>
Version: 7.1.0 Date: December 2025 Migration Type: Process Management (PM2 → Bun) + Database Driver (better-sqlite3 → bun:sqlite)
Claude-mem version 7.1.0 introduces two major architectural migrations:
Both migrations are automatic and transparent to end users. The first time a hook fires after updating to 7.1.0+, the system performs a one-time cleanup of legacy PM2 processes and transitions to the new architecture.
Lifecycle Commands:
pm2 start <script> # Start worker
pm2 stop claude-mem-worker # Stop worker
pm2 restart claude-mem-worker # Restart worker
pm2 delete claude-mem-worker # Remove from PM2
pm2 logs claude-mem-worker # View logs
Pain Points:
Installation Requirements:
Lifecycle Commands:
npm run worker:start # Start worker
npm run worker:stop # Stop worker
npm run worker:restart # Restart worker
npm run worker:status # Check status
npm run worker:logs # View logs
Core Mechanisms:
PID File Management:
~/.claude-mem/.worker.pidkill(pid, 0) signalPort File Management:
~/.claude-mem/.worker.portHealth Checking:
kill(pid, 0))GET /health)Advantages:
Installation Requirements:
Compatibility:
~/.claude-mem/claude-mem.db (same location)The migration system uses a marker-based approach to perform PM2 cleanup exactly once.
Implementation: src/shared/worker-utils.ts:73-86
// Clean up legacy PM2 (one-time migration)
const pm2MigratedMarker = join(DATA_DIR, '.pm2-migrated');
if (!existsSync(pm2MigratedMarker)) {
try {
spawnSync('pm2', ['delete', 'claude-mem-worker'], { stdio: 'ignore' });
// Mark migration as complete
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
logger.debug('SYSTEM', 'PM2 cleanup completed and marked');
} catch {
// PM2 not installed or process doesn't exist - still mark as migrated
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
}
}
Location: ~/.claude-mem/.pm2-migrated
Content: ISO 8601 timestamp
2025-12-13T00:18:39.673Z
Purpose:
Lifecycle:
Step-by-Step Execution:
pm2 delete claude-mem-worker (old worker terminated)~/.claude-mem/.pm2-migrated with timestampUser Observable Behavior:
npm run worker:statuspm2 listAfter migration completes, every hook trigger follows the fast path:
No migration logic runs on subsequent sessions.
| Feature | macOS | Linux | Windows |
|---|---|---|---|
| PM2 Cleanup | Attempted | Attempted | Attempted |
| Marker File | Created | Created | Created |
| Process Signals | POSIX (native) | POSIX (native) | Bun abstraction |
| Bun Support | Full | Full | Full |
| PID File | Yes | Yes | Yes |
| Port File | Yes | Yes | Yes |
| Health Check | HTTP | HTTP | HTTP |
| Migration Delay | ~2-5s first time | ~2-5s first time | ~2-5s first time |
| Old (PM2) | New (Bun) | Notes |
|---|---|---|
pm2 list | npm run worker:status | Shows worker status |
pm2 start <script> | npm run worker:start | Start worker |
pm2 stop claude-mem-worker | npm run worker:stop | Stop worker |
pm2 restart claude-mem-worker | npm run worker:restart | Restart worker |
pm2 delete claude-mem-worker | npm run worker:stop | Remove worker |
pm2 logs claude-mem-worker | npm run worker:logs | View logs |
pm2 describe claude-mem-worker | npm run worker:status | Detailed status |
pm2 monit | No equivalent | PM2-specific monitoring |
Logs:
Old: ~/.pm2/logs/claude-mem-worker-out.log
~/.pm2/logs/claude-mem-worker-error.log
New: ~/.claude-mem/logs/worker-YYYY-MM-DD.log
PID Files:
Old: ~/.pm2/pids/claude-mem-worker.pid
New: ~/.claude-mem/.worker.pid
Process State:
Old: PM2 daemon memory (pm2 save)
New: ~/.claude-mem/.worker.pid
~/.claude-mem/.worker.port
~/.claude-mem/.pm2-migrated (all platforms)
Database (unchanged):
Same: ~/.claude-mem/claude-mem.db
Before Update:
$ pm2 list
┌────┬────────────────────┬─────────┬─────────┬──────────┐
│ id │ name │ status │ restart │ uptime │
├────┼────────────────────┼─────────┼─────────┼──────────┤
│ 0 │ claude-mem-worker │ online │ 0 │ 2d 5h │
└────┴────────────────────┴─────────┴─────────┴──────────┘
After Update:
$ pm2 list
# Empty - worker no longer managed by PM2
$ npm run worker:status
Worker is running
PID: 35557
Port: 37777
Uptime: 2h 15m
After migration, these PM2 files may remain (safe to delete):
~/.pm2/ # Entire PM2 directory
~/.pm2/logs/ # Old logs
~/.pm2/pids/ # Old PID files
~/.pm2/pm2.log # PM2 daemon log
~/.pm2/dump.pm2 # PM2 process dump
Cleanup (optional):
# Remove PM2 entirely (if not used for other processes)
pm2 kill
rm -rf ~/.pm2
# Or just remove claude-mem logs
rm -f ~/.pm2/logs/claude-mem-worker-*.log
rm -f ~/.pm2/pids/claude-mem-worker.pid
Before Migration (PM2 system):
~/.claude-mem/
├── claude-mem.db # Database (unchanged)
├── chroma/ # Vector embeddings (unchanged)
├── logs/ # Application logs (unchanged)
└── settings.json # User settings (unchanged)
~/.pm2/
├── logs/
│ ├── claude-mem-worker-out.log
│ └── claude-mem-worker-error.log
├── pids/
│ └── claude-mem-worker.pid
└── pm2.log
After Migration (Bun system):
~/.claude-mem/
├── claude-mem.db # Database (same file)
├── chroma/ # Vector embeddings (unchanged)
├── logs/
│ └── worker-2025-12-13.log # New log format
├── settings.json # User settings (unchanged)
├── .worker.pid # NEW: Process ID
├── .worker.port # NEW: Port + PID
└── .pm2-migrated # NEW: Migration marker (all platforms)
~/.pm2/ # Orphaned (safe to delete)
├── logs/ # Old logs (no longer written)
├── pids/ # Old PID (no longer updated)
└── pm2.log # PM2 daemon log (not used)
Symptoms:
pm2 list still shows claude-mem-workerResolution:
# Manual cleanup
pm2 delete claude-mem-worker
pm2 save # Persist the deletion
# Force re-migration (optional)
rm ~/.claude-mem/.pm2-migrated
# Restart worker
npm run worker:restart
Symptoms:
npm run worker:status shows "not running".worker.pid file existsAutomatic Recovery: Next hook trigger detects dead process and starts a fresh worker.
Manual Resolution:
rm ~/.claude-mem/.worker.pid
rm ~/.claude-mem/.worker.port
npm run worker:start
Error: EADDRINUSE: address already in use
Resolution:
# Check what's using the port
lsof -i :37777
# Kill the process
kill -9 <PID>
# Restart worker
npm run worker:restart
| Error | Cause | Resolution |
|---|---|---|
EADDRINUSE | Port already in use | lsof -i :37777 then kill conflicting process |
No such process | Stale PID file | Automatic cleanup on next hook trigger |
pm2: command not found | PM2 not installed | None needed (error is caught and ignored) |
Invalid port X | Port validation failed | Update CLAUDE_MEM_WORKER_PORT in settings |
# 1. Install old version (with PM2)
git checkout <pre-7.1.0-tag>
npm install && npm run build && npm run sync-marketplace
# 2. Start PM2 worker
pm2 start plugin/scripts/worker-cli.js --name claude-mem-worker
# 3. Update to new version
git checkout main
npm install && npm run build && npm run sync-marketplace
# 4. Trigger hook
node plugin/scripts/session-start-hook.js
# 5. Verify migration
pm2 list # Should NOT show claude-mem-worker
cat ~/.claude-mem/.pm2-migrated # Should exist
npm run worker:status # Should show Bun worker running
Why Custom ProcessManager Instead of PM2?
Why One-Time Marker Instead of Always Running PM2 Delete?
Why Run PM2 Cleanup on All Platforms?
The migration from PM2 to Bun-based ProcessManager is a one-time, automatic, transparent transition that:
Key Migration Moment: First hook trigger after update to 7.1.0+ Duration: ~2-5 seconds (one-time delay) Impact: Seamless transition, user-invisible Rollback: Not needed (migration is forward-only, safe)
For most users, the migration will be completely transparent - they'll see no errors, no data loss, and experience improved reliability and simpler troubleshooting going forward.