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
- Remove the symlink and restore a real binary at that path: `rm <path>` then reinstall with installNasiko({yes:true}).
- Run uninstallNasiko to clear the managed path, then reinstall.
- 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
- Do not symlink managed binaries into the install directory; use PATH entries pointing at real copies.
- Uninstall before reorganizing versions instead of leaving symlinks behind.
- Check `ls -l` on managed paths after using version managers.
- Reinstall via the library rather than copying/linking binaries manually.
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
- gate.variant_invalid
- Invalid ECC repo root: missing package.json at
- Nasiko install directory must be a real directory, not a…
- output artifact must be a regular file
- output bundle contains a symlink
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)