Claude MPM includes an automatic migration system that runs on first startup after an update. Migrations handle configuration changes, cache directory restructuring, and other one-time fixes needed between versions.
Migrations execute automatically during startup:
- First in startup sequence - Before agent sync or any other operations
- Once per installation - Each migration runs only once
- Tracked persistently - Completed migrations are recorded in
~/.claude-mpm/migrations.yaml
- Non-blocking: Migration failures log warnings but do not stop startup
- Idempotent: Safe to run multiple times (condition checked before migrating)
- Tracked: Each migration has a unique ID and runs only once
- Early: Runs before agent sync to ensure correct configuration
Migrations handle various configuration and cache updates:
| Migration ID | Version | Description |
|---|---|---|
v5.6.76-cache-dir-rename |
5.6.76 | Renames remote-agents/ to agents/ in cache directory |
skill_scope_v1 |
6.2.1+ | Detects user-scoped Claude Code plugins bleeding into unrelated project sessions; offers opt-in auto-fix to move them to project scope |
This migration:
- Moves
~/.claude-mpm/cache/remote-agents/contents to~/.claude-mpm/cache/agents/ - Removes the old
remote-agentsdirectory - Updates
configuration.yamlif it references the old path
This migration addresses a problem where Claude Code plugins installed at user scope can appear in sessions for unrelated projects, potentially injecting unexpected context or tools.
What it checks:
- Reads the global
~/.claude/plugins/installed_plugins.json - Identifies plugins scoped as
"scope": "user"whose plugin name does not match the current project name
What it does (opt-in): For each foreign user-scoped plugin found, the user is prompted to move it to project scope:
- Removes the entry from the user-scope list in the global
installed_plugins.json - Adds a project-scoped entry in
{CWD}/.claude/plugins/installed_plugins.json
When it applies:
- Only runs once per project (tracked by migration ID)
- Only triggers if foreign user-scoped plugins are present
- All moves are opt-in; the user is prompted for each plugin
Completed migrations are tracked in ~/.claude-mpm/migrations.yaml:
migrations:
- id: v5.6.76-cache-dir-rename
completed_at: '2026-01-23T10:30:00.000000+00:00'When migrations apply, you will see a notification during startup:
Starting startup services...
Running startup migration: Rename remote-agents cache dir to agents
Syncing agents from 1 remote sources...
If no migrations are needed (already completed or conditions don't apply), startup proceeds silently without any migration messages.
If a migration fails, you will see a warning in the logs:
[WARNING] Migration v5.6.76-cache-dir-rename failed
What to do:
- The failure is non-blocking; startup continues normally
- Check the log file at
~/.claude-mpm/logs/claude-mpm.logfor details - Most failures are due to permission issues or file locks
- Manually perform the migration if needed (see specific migration docs)
To re-run a migration:
- Edit
~/.claude-mpm/migrations.yaml - Remove the entry for the specific migration
- Restart claude-mpm
# Example: Remove cache-dir-rename migration to re-run it
sed -i '' '/v5.6.76-cache-dir-rename/d' ~/.claude-mpm/migrations.yamlView completed migrations:
cat ~/.claude-mpm/migrations.yamlTo add a new migration, edit src/claude_mpm/cli/startup_migrations.py:
def _check_my_migration_needed() -> bool:
"""Check if migration is needed.
Returns:
True if migration should run.
"""
# Check condition (e.g., old file exists)
return Path.home() / ".claude-mpm" / "old_file.yaml".exists()
def _migrate_my_migration() -> bool:
"""Perform the migration.
Returns:
True if migration succeeded.
"""
try:
# Perform migration steps
old_file = Path.home() / ".claude-mpm" / "old_file.yaml"
new_file = Path.home() / ".claude-mpm" / "new_file.yaml"
shutil.move(str(old_file), str(new_file))
return True
except Exception as e:
logger.warning(f"Migration failed: {e}")
return FalseAdd to the MIGRATIONS list:
MIGRATIONS: list[Migration] = [
# Existing migrations...
Migration(
id="v5.7.0-my-migration",
description="Description shown during startup",
check=_check_my_migration_needed,
migrate=_migrate_my_migration,
),
]Use the format: v{VERSION}-{short-description}
Examples:
v5.6.76-cache-dir-renamev5.7.0-config-schema-updatev6.0.0-major-restructure
- Always check first: The
checkfunction should returnFalseif migration is not needed - Handle errors gracefully: Catch exceptions and return
Falseon failure - Log appropriately: Use
logger.debug()for success details,logger.warning()for failures - Keep migrations focused: One logical change per migration
- Test thoroughly: Add tests in
tests/cli/test_startup_migrations.py
- Implementation:
src/claude_mpm/cli/startup_migrations.py - Integration:
src/claude_mpm/cli/startup.py - Tests:
tests/cli/test_startup_migrations.py - Tracking:
~/.claude-mpm/migrations.yaml