affaan-m/ECC · error

Refusing to remove an unqualified or modified Nasiko…

Error message

Refusing to remove an unqualified or modified Nasiko executable.

What it means

During uninstall, the existing executable at the destination is inspected and must be a qualified Nasiko binary of exactly the requested version; otherwise the installer refuses to delete it. This protects against removing user-modified, corrupted, or unrelated files that happen to occupy the install path.

Solutions

  1. Pass the exact version currently installed (check the receipt/metadata) instead of assuming a different version.
  2. Reinstall the matching qualified version first if the binary was corrupted, then uninstall cleanly.
  3. Manually remove the destination file and its metadata receipt if you are certain the file is safe to delete.
  4. Verify no post-install process modified the binary (permissions, patches, antivirus quarantine) before retrying.

Example fix

// before
await uninstallNasiko({ version: 'v0.2.0', yes: true }); // throws: destination holds v0.1.0
// after
const status = inspectInstalledNasiko(destination);
await uninstallNasiko({ version: status.version, yes: true });
Defensive patterns

Strategy: validation

Validate before calling

const status = inspectInstalledNasiko(destination);
if (!status.installed || !status.qualified || status.version !== targetVersion) {
  throw new Error(`cannot uninstall: installed=${status.installed} qualified=${status.qualified} version=${status.version}`);
}

Type guard

function isQualifiedVersion(status, version) {
  return Boolean(status) && status.installed === true && status.qualified === true && status.version === version;
}

Try / catch

try {
  await uninstallNasiko({ version, yes: true });
} catch (e) {
  if (e.message.includes('unqualified or modified')) {
    // inspect receipt, then uninstall the actual installed version or remove manually
  } else throw e;
}

Prevention

When it happens

Trigger: inspectInstalledNasiko(destination) reports installed=true but status.qualified is false, or status.version !== the version passed to uninstallNasiko.

Common situations: Trying to uninstall a version that was already replaced by an upgrade; the binary was modified/patched after install so qualification fails; a non-Nasiko file occupies the destination path; a partial/corrupted install.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/1d9343b3e1d150bc. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/nasiko-release.js:450

  const destination = path.join(installDirectory, release.binaryName);
  const plan = { dryRun: Boolean(options.dryRun), version, destination };
  if (options.dryRun) return plan;
  if (!options.yes) throw new Error('Nasiko uninstall requires explicit --yes consent.');
  if (!fs.existsSync(installDirectory)) return { ...plan, dryRun: false, removed: false };
  assertPrivateInstallDirectory(installDirectory);
  const releaseLock = acquireLifecycleLock(installDirectory);
  const metadataPath = metadataPathFor(destination);
  const suffix = `${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
  const binaryTombstone = `${destination}.remove-${suffix}`;
  const metadataTombstone = `${metadataPath}.remove-${suffix}`;
  let binaryStaged = false;
  let metadataStaged = false;
  const rename = dependencies.rename || fs.renameSync;
  const remove = dependencies.remove || (target => fs.rmSync(target));
  try {
    const status = (dependencies.inspectInstalled || inspectInstalledNasiko)(destination);
    if (!status.installed) return { ...plan, dryRun: false, removed: false };
    if (!status.qualified || status.version !== version) throw new Error('Refusing to remove an unqualified or modified Nasiko executable.');
    rename(destination, binaryTombstone);
    binaryStaged = true;
    rename(metadataPath, metadataTombstone);
    metadataStaged = true;
    const cleanupPending = [];
    try { remove(metadataTombstone); } catch (_error) { cleanupPending.push(metadataTombstone); }
    metadataStaged = false;
    try { remove(binaryTombstone); } catch (_error) { cleanupPending.push(binaryTombstone); }
    binaryStaged = false;
    if (cleanupPending.length > 0) {
      const cleanupError = new Error(`Nasiko uninstall is incomplete; retained staged file(s): ${cleanupPending.join(', ')}. Remove these files before reinstalling.`);
      cleanupError.cleanupPending = cleanupPending;
      throw cleanupError;
    }
    return { ...plan, dryRun: false, removed: true, cleanupPending: [] };
  } catch (error) {
    if (metadataStaged && !fs.existsSync(metadataPath)) rename(metadataTombstone, metadataPath);
    if (binaryStaged && !fs.existsSync(destination)) rename(binaryTombstone, destination);

View on GitHub (pinned to 8321021c54)