affaan-m/ECC · error · Error

Invalid ECC repo root: unreadable package.json at ${packageJ

Error message

Invalid ECC repo root: unreadable package.json at ${packageJsonPath}

What it means

The scope-migration tool moves the ECC Claude plugin from one scope (user/project/local) to another, then runs verifyFinalState() to re-read the plugin inventory and confirm the plugin now exists ONLY in the destination scope. This error (code FINAL_VERIFICATION_FAILED) fires when that post-migration inventory still shows a source-scope copy, shows the plugin in both scopes, or shows it missing from the destination — i.e. the migration did not converge to a clean destination-only state. It carries structured recovery commands.

Source

Thrown at scripts/auto-update.js:147

function validateRepoRoot(repoRoot) {
  const normalized = path.resolve(repoRoot);
  const packageJsonPath = path.join(normalized, 'package.json');
  const installApplyPath = path.join(normalized, 'scripts', 'install-apply.js');

  if (!fs.existsSync(packageJsonPath)) {
    throw new Error(`Invalid ECC repo root: missing package.json at ${packageJsonPath}`);
  }

  if (!fs.existsSync(installApplyPath)) {
    throw new Error(`Invalid ECC repo root: missing install script at ${installApplyPath}`);
  }

  let pkgName = null;
  try {
    pkgName = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).name;
  } catch {
    throw new Error(`Invalid ECC repo root: unreadable package.json at ${packageJsonPath}`);
  }
  if (!ECC_PACKAGE_NAMES.has(pkgName)) {
    throw new Error(`Refusing to run install from untrusted repo root ${normalized}: package.json name '${pkgName}' is not an official ECC package.`);
  }

  return normalized;
}

function runExternalCommand(command, args, options = {}) {
  const result = spawnSync(command, args, {
    cwd: options.cwd,
    env: options.env || process.env,
    encoding: 'utf8',
    maxBuffer: 10 * 1024 * 1024
  });

  if (result.error) {
    throw result.error;

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Run the structured recovery commands embedded in the error object (err.recovery / recoveryCommands(null, destinationScope)) — typically a `claude plugin uninstall` of the source scope followed by an install in the destination.
  2. Manually open the relevant settings.json (user: ~/.claude/settings.json, project: .claude/settings.json, local) and remove the stale source-scope ECC plugin entry, leaving only the destination one.
  3. Delete or refresh any stale local plugin inventory cache, then re-run the migration.
  4. Re-run migrateClaudePluginScope with the same scope once the conflicting entry is removed.

Example fix

// before
await migrateClaudePluginScope({ scope: 'project' }); // throws FINAL_VERIFICATION_FAILED

// after — handle structured recovery
try {
  await migrateClaudePluginScope({ scope: 'project' });
} catch (err) {
  if (err.code === 'FINAL_VERIFICATION_FAILED') {
    for (const cmd of err.recovery) console.log('Run:', cmd);
    process.exit(1);
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Type guard

// Detect a migration error by its structured code field.
function isFinalVerificationError(e) {
  return e && e.code === 'FINAL_VERIFICATION_FAILED';
}

Try / catch

try {
  await migrateClaudePluginScope({ scope: 'project' });
} catch (err) {
  if (err.code === 'FINAL_VERIFICATION_FAILED') {
    // err.recovery holds the exact uninstall/install commands to run.
    console.error('Migration did not converge. Run these commands:');
    for (const cmd of (err.recovery || [])) console.error('  ' + cmd);
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Running migrateClaudePluginScope({ scope: 'project' }) after the destination install succeeded but the source-scope uninstall failed silently; manual edits to settings.json leaving duplicate enabled entries; an interrupted migration that installed the destination but never removed the source; a conflicting/stale local plugin inventory file.

Common situations: A previous migration was interrupted; settings.json was hand-edited so the plugin appears in two scopes; a stale ~/.claude/plugins local inventory disagrees with the real settings; Claude CLI reported a successful uninstall but the entry persisted.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/79d3c01e282c3174. Report an issue: GitHub.