affaan-m/ECC · error

Nasiko executable must be a regular file, not a symlink.

Error message

Nasiko executable must be a regular file, not a symlink.

What it means

inspectInstalledNasiko reads the executable with O_NOFOLLOW (and a fallback lstat identity check when O_NOFOLLOW is unavailable). If the read fails with ELOOP — meaning the path is a symlink, or the file was swapped for a symlink mid-read — it throws this error instead of silently following the link, preventing symlink-based binary swapping attacks.

Solutions

  1. Remove the symlink and restore a real binary at that path: `rm <path>` then reinstall with installNasiko({yes:true}).
  2. Run uninstallNasiko to clear the managed path, then reinstall.
  3. Inspect with `ls -l <path>`; if it points at a legitimate copy, pass the real file path to inspect and remove the link.

Example fix

// before
ln -s /opt/nasiko-0.1.0/nasiko ~/.local/bin/nasiko
inspectInstalledNasiko(path.join(os.homedir(), '.local/bin/nasiko')) // throws
// after
rm ~/.local/bin/nasiko
installNasiko({ version: 'v0.1.0', yes: true })
Defensive patterns

Strategy: validation

Validate before calling

const stats = fs.lstatSync(executablePath)
if (stats.isSymbolicLink()) throw new Error(`managed path must not be a symlink: ${executablePath}`)

Type guard

const isRegularNonSymlinkFile = (p) => {
  try { const s = fs.lstatSync(p); return s.isFile() && !s.isSymbolicLink() } catch { return false }
}

Try / catch

try {
  const status = inspectInstalledNasiko(executablePath)
} catch (e) {
  if (e.message.includes('regular file, not a symlink')) {
    fs.rmSync(executablePath)
    return installNasiko({ yes: true }) // reinstall a real binary
  }
  throw e
}

Prevention

When it happens

Trigger: Calling inspectInstalledNasiko (directly or via existing/readStatus/tests) with an executable path that is a symbolic link; or the nasiko binary at the destination being replaced by a symlink (e.g. someone linked it to another tool, or a package manager symlinked it).

Common situations: Users symlink ~/bin/nasiko to a version in another directory (common version-manager pattern); a previous install was moved and a symlink left behind; package managers like Homebrew symlinking binaries into bin directories.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

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

    return bytes;
  } finally { fs.closeSync(descriptor); }
}

function readMetadata(executable) {
  try {
    const bytes = readBoundedRegularFile(metadataPathFor(executable), MAX_METADATA_BYTES);
    return bytes ? JSON.parse(bytes.toString('utf8')) : null;
  }
  catch (_error) { return null; }
}

function inspectInstalledNasiko(executable, resolveRelease = getQualifiedRelease) {
  if (!executable) return { installed: false, qualified: false, version: null, executable: null };
  let binary;
  try { binary = readBoundedRegularFile(executable, MAX_BINARY_BYTES); }
  catch (error) {
    if (error.code === 'ENOENT') return { installed: false, qualified: false, version: null, executable };
    if (error.code === 'ELOOP') throw new Error('Nasiko executable must be a regular file, not a symlink.');
    throw error;
  }
  if (!binary) return { installed: true, qualified: false, version: null, executable, binaryDigest: null, metadataPath: metadataPathFor(executable) };
  const binaryDigest = digestBytes(binary);
  const metadata = readMetadata(executable);
  let release = null;
  try { if (metadata) release = resolveRelease(metadata.version, metadata.platform, metadata.architecture); } catch (_error) { release = null; }
  const qualified = Boolean(release
    && metadata.installedPath === executable
    && metadata.manifestDigest === release.manifestDigest
    && metadata.binaryDigest === release.binaryDigest
    && binaryDigest === release.binaryDigest
    && metadata.license === release.license
    && metadata.sourceUrl === release.sourceUrl);
  return { installed: true, qualified, version: qualified ? metadata.version : null, executable, binaryDigest, metadataPath: metadataPathFor(executable) };
}

function writeMetadataExclusive(metadataPath, metadata) {

View on GitHub (pinned to 8321021c54)