affaan-m/ECC · error
SOURCE_UNINSTALL_FAILED
SOURCE_UNINSTALL_FAILED
Error message
The destination is installed, but Claude could not remove the ${migration.sourceScope} source scope. What it means
ClaudeSetupError with code SOURCE_UNINSTALL_FAILED thrown by uninstallSource in scripts/lib/claude-scope-migration.js:223. During a two-scope ('resume') migration, the destination is already installed, but Claude must remove the old source-scope copy. If `claude plugin uninstall` fails or the post-uninstall inventory re-verification throws, the migrator raises this error describing the safe state (destination installed, source still present) and includes recovery commands for the source scope. The code path intentionally preserves the two-scope state so a retry can resume rather than leaving the user with nothing installed.
Solutions
- Retry the removal manually as the error suggests: `claude plugin uninstall <plugin-id> --scope <sourceScope> --keep-data`, then re-run the migration.
- Close any running Claude Code sessions that may hold the plugin, then retry the uninstall.
- Check write permissions on Claude's config/plugin directories for the source scope and fix them if uninstall failed on I/O.
- If Claude reports an uninstall error but the destination-only state is actually verified (per the code), re-run the migration — it re-checks the inventory and may proceed as already-migrated.
Example fix
// before: migration fails removing source scope 'user' // after // claude plugin uninstall everything-claude-code --scope user --keep-data // ecc migrate-scope --scope project # resumes and completes
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: ensure no other Claude process holds the plugin and source scope is removable
const plugins = parsePluginList(run(['plugin', 'list', '--json'], { cwd }).stdout);
if (plugins.some(p => p.scope === sourceScope)) {
run(['plugin', 'uninstall', CURRENT_PLUGIN_ID, '--scope', sourceScope, '--keep-data'], { cwd });
} Type guard
function isSourceRemoved(plugins, sourceScope) { return !plugins.some(p => p.scope === sourceScope); } Try / catch
try { await migrateScope({ scope }) }
catch (e) {
if (e.code === 'SOURCE_UNINSTALL_FAILED') {
// safe two-scope state preserved; retry after manual uninstall
console.log('Run:', `claude plugin uninstall ${CURRENT_PLUGIN_ID} --scope <source> --keep-data`, 'then retry');
} else throw e;
} Prevention
- Close running Claude Code sessions before migrating scopes.
- Verify write permissions on Claude config/plugin directories for both scopes.
- Run the uninstall manually first if the source scope is known to be problematic.
- Never delete config directories mid-migration; let the tool preserve resumable state.
When it happens
Trigger: Running a scope migration in resume mode where `claude plugin uninstall <id> --scope <sourceScope>` returns an error (non-zero exit, stderr output) or throws, and re-checking the plugin list still shows the source-scope installation alongside the destination.
Common situations: The plugin is locked because a Claude session has it loaded; filesystem permission problems in Claude's plugin cache directory; a stale/corrupt plugin registry entry that uninstall cannot remove; running uninstall while another Claude process holds the config.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- AMBIGUOUS_PLUGIN_SCOPES
- PLUGIN_NOT_INSTALLED
- DESTINATION_VERIFICATION_FAILED
- INVALID_SCOPE
- Agents directory not found
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/77c18f7a54a8cfac.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/claude-scope-migration.js:223
const plugins = readPluginInventory(
run,
paths.projectRoot,
'source-uninstall-verification'
);
assertNoConflictingEccPlugins(plugins);
const installed = currentEccPlugins(plugins);
observedScopes = installed.map(plugin => plugin.scope);
if (
installed.length === 1
&& installed[0].scope === destinationScope
&& installed[0].enabled === true
) {
return ['Claude reported an uninstall error, but destination-only state was verified.'];
}
} catch {
// Preserve the safest known two-scope state in the structured recovery.
}
throw migrationError(
'SOURCE_UNINSTALL_FAILED',
`The destination is installed, but Claude could not remove the ${migration.sourceScope} source scope.`,
{
phase: 'source-uninstall',
observedScopes,
recovery: recoveryCommands(migration.sourceScope, destinationScope),
}
);
}
}
function verifyFinalState(run, paths, destinationScope) {
const plugins = readPluginInventory(run, paths.projectRoot, 'final-verification');
return validateExpectedScopes(plugins, [destinationScope], {
code: 'FINAL_VERIFICATION_FAILED',
destinationScope,
message: `Could not verify destination-only ${CURRENT_PLUGIN_ID} state after source cleanup.`,
phase: 'final-verification',View on GitHub (pinned to 8321021c54)