{"record":{"id":"1d9343b3e1d150bc","repo":"affaan-m/ECC","slug":"refusing-to-remove-an-unqualified-or-modified-nasi","errorCode":null,"errorMessage":"Refusing to remove an unqualified or modified Nasiko executable.","messagePattern":"Refusing to remove an unqualified or modified Nasiko executable\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/nasiko-release.js","lineNumber":450,"sourceCode":"  const destination = path.join(installDirectory, release.binaryName);\n  const plan = { dryRun: Boolean(options.dryRun), version, destination };\n  if (options.dryRun) return plan;\n  if (!options.yes) throw new Error('Nasiko uninstall requires explicit --yes consent.');\n  if (!fs.existsSync(installDirectory)) return { ...plan, dryRun: false, removed: false };\n  assertPrivateInstallDirectory(installDirectory);\n  const releaseLock = acquireLifecycleLock(installDirectory);\n  const metadataPath = metadataPathFor(destination);\n  const suffix = `${process.pid}-${crypto.randomBytes(6).toString('hex')}`;\n  const binaryTombstone = `${destination}.remove-${suffix}`;\n  const metadataTombstone = `${metadataPath}.remove-${suffix}`;\n  let binaryStaged = false;\n  let metadataStaged = false;\n  const rename = dependencies.rename || fs.renameSync;\n  const remove = dependencies.remove || (target => fs.rmSync(target));\n  try {\n    const status = (dependencies.inspectInstalled || inspectInstalledNasiko)(destination);\n    if (!status.installed) return { ...plan, dryRun: false, removed: false };\n    if (!status.qualified || status.version !== version) throw new Error('Refusing to remove an unqualified or modified Nasiko executable.');\n    rename(destination, binaryTombstone);\n    binaryStaged = true;\n    rename(metadataPath, metadataTombstone);\n    metadataStaged = true;\n    const cleanupPending = [];\n    try { remove(metadataTombstone); } catch (_error) { cleanupPending.push(metadataTombstone); }\n    metadataStaged = false;\n    try { remove(binaryTombstone); } catch (_error) { cleanupPending.push(binaryTombstone); }\n    binaryStaged = false;\n    if (cleanupPending.length > 0) {\n      const cleanupError = new Error(`Nasiko uninstall is incomplete; retained staged file(s): ${cleanupPending.join(', ')}. Remove these files before reinstalling.`);\n      cleanupError.cleanupPending = cleanupPending;\n      throw cleanupError;\n    }\n    return { ...plan, dryRun: false, removed: true, cleanupPending: [] };\n  } catch (error) {\n    if (metadataStaged && !fs.existsSync(metadataPath)) rename(metadataTombstone, metadataPath);\n    if (binaryStaged && !fs.existsSync(destination)) rename(binaryTombstone, destination);","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/nasiko-release.js#L432-L468","documentation":"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.","triggerScenarios":"inspectInstalledNasiko(destination) reports installed=true but status.qualified is false, or status.version !== the version passed to uninstallNasiko.","commonSituations":"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.","solutions":["Pass the exact version currently installed (check the receipt/metadata) instead of assuming a different version.","Reinstall the matching qualified version first if the binary was corrupted, then uninstall cleanly.","Manually remove the destination file and its metadata receipt if you are certain the file is safe to delete.","Verify no post-install process modified the binary (permissions, patches, antivirus quarantine) before retrying."],"exampleFix":"// before\nawait uninstallNasiko({ version: 'v0.2.0', yes: true }); // throws: destination holds v0.1.0\n// after\nconst status = inspectInstalledNasiko(destination);\nawait uninstallNasiko({ version: status.version, yes: true });","handlingStrategy":"validation","validationCode":"const status = inspectInstalledNasiko(destination);\nif (!status.installed || !status.qualified || status.version !== targetVersion) {\n  throw new Error(`cannot uninstall: installed=${status.installed} qualified=${status.qualified} version=${status.version}`);\n}","typeGuard":"function isQualifiedVersion(status, version) {\n  return Boolean(status) && status.installed === true && status.qualified === true && status.version === version;\n}","tryCatchPattern":"try {\n  await uninstallNasiko({ version, yes: true });\n} catch (e) {\n  if (e.message.includes('unqualified or modified')) {\n    // inspect receipt, then uninstall the actual installed version or remove manually\n  } else throw e;\n}","preventionTips":["Track the installed version and uninstall exactly that version","Do not modify, chmod-strip, or patch the binary after install","Inspect the receipt metadata before uninstalling","Only remove destination files manually when you are certain they are Nasiko artifacts"],"tags":["uninstall","safety-guard","version-mismatch"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}