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

  1. Retry the removal manually as the error suggests: `claude plugin uninstall <plugin-id> --scope <sourceScope> --keep-data`, then re-run the migration.
  2. Close any running Claude Code sessions that may hold the plugin, then retry the uninstall.
  3. Check write permissions on Claude's config/plugin directories for the source scope and fix them if uninstall failed on I/O.
  4. 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

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


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)