affaan-m/ECC · error

Nasiko cannot install directly into a filesystem root.

Error message

Nasiko cannot install directly into a filesystem root.

What it means

The third gate in validateInstallDirectory resolves the directory and refuses when the result equals the filesystem root (path.parse(resolved).root, e.g. '/' or 'C:\'). Installing 'directly into root' would scatter binary and metadata files at the top of the drive, which is destructive and almost always a path-construction bug, so it is rejected before the ancestor-resolution logic runs.

Source

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

    });
    request.setTimeout(options.timeoutMs || 15000, () => request.destroy(new Error('Nasiko registry request timed out.')));
    request.on('error', reject);
  });
}

function defaultInstallDirectory(normalized, environment = process.env, homeDirectory = os.homedir()) {
  if (normalized.os === 'windows') {
    if (!environment.LOCALAPPDATA) throw new Error('LOCALAPPDATA is required on Windows.');
    return path.join(environment.LOCALAPPDATA, 'nasiko', 'bin');
  }
  return path.join(homeDirectory, '.local', 'bin');
}

function validateInstallDirectory(directory) {
  if (typeof directory !== 'string' || directory.includes('\0') || !path.isAbsolute(directory)) throw new Error('Nasiko install directory must be an absolute path.');
  if (/^(?:\\\\|\\\\\?\\|\\\\\.\\)/.test(directory)) throw new Error('Nasiko install directory must be on a local filesystem.');
  const resolved = path.resolve(directory);
  if (resolved === path.parse(resolved).root) throw new Error('Nasiko cannot install directly into a filesystem root.');
  let ancestor = resolved;
  while (!fs.existsSync(ancestor)) {
    const parent = path.dirname(ancestor);
    if (parent === ancestor) throw new Error('Nasiko install directory has no resolvable filesystem ancestor.');
    ancestor = parent;
  }
  const canonical = fs.realpathSync(ancestor);
  return path.join(canonical, path.relative(ancestor, resolved));
}

function assertPrivateInstallDirectory(directory) {
  const stats = fs.lstatSync(directory);
  if (!stats.isDirectory() || stats.isSymbolicLink()) throw new Error('Nasiko install directory must be a real directory, not a symlink.');
  if (process.platform !== 'win32') {
    if (typeof process.getuid === 'function' && stats.uid !== process.getuid()) throw new Error('Nasiko install directory must be owned by the current user.');
    if ((stats.mode & 0o022) !== 0) throw new Error('Nasiko install directory must not be group- or world-writable.');
  }
}

View on GitHub (pinned to 06c5e118c4)

Solutions

  1. Pass a real subdirectory, e.g. path.join(base || os.homedir(), '.local', 'bin')
  2. Guard config-derived paths: if the resolved value equals the root, fall back to the default install directory
  3. Log the computed directory before invoking the installer so misconfiguration is visible

Example fix

// before: empty subPath collapses the join to the root
const dir = path.join(base, subPath); // subPath === ''
// after: guarantee a subdirectory
const dir = path.join(base, subPath || 'nasiko', 'bin');
Defensive patterns

Strategy: validation

Validate before calling

function assertNotRootDir(dir) {
  const resolved = path.resolve(dir);
  if (resolved === path.parse(resolved).root) {
    throw new Error(`Refusing install directory at filesystem root: ${dir}`);
  }
  return resolved;
}

Try / catch

try {
  await installNasiko({ directory: computedDir });
} catch (error) {
  if (/cannot install directly into a filesystem root/.test(String(error.message))) {
    // A path-join produced '/' or 'C:\'; fix the base/subpath inputs and retry.
  }
  throw error;
}

Prevention

When it happens

Trigger: path.resolve(directory) === path.parse(path.resolve(directory)).root. Happens when a base path is empty and the join collapses to '/', when a Windows config yields 'C:\', or when directory is exactly '/'.

Common situations: path.join(base, sub) where base is '' and sub is '' or '/'; misparsed CLI flags like --dir=/; Windows configs where the drive letter is kept but the subpath variable is empty; container setups that pass '/' intending 'install anywhere'.

Related errors


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/c8537e5f10d4bf02. Report an issue: GitHub.