affaan-m/ECC · error

Refusing to install ECC file through symlinked path

Error message

Refusing to install ECC file through symlinked path: '${currentPath}'.

What it means

As part of assertSafeInstallOperation, the installer walks each path segment from the resolved target root and lstat-checks it; if any existing segment is a symbolic link, installing through it is refused. This prevents an attacker (or a misconfigured environment) from redirecting ECC file writes outside the trusted target root via a symlinked directory or file. The error names the offending path prefix.

Solutions

  1. Replace the symlinked segment with a real directory (e.g. remove the link, mkdir the real directory) or point targetRoot at the actual directory.
  2. Install directly into the physical location the symlink points to, passing that as targetRoot.
  3. Use a non-symlinked install target (or the target's native mechanism) for dotfiles management.
  4. Find the offending segment with 'namei -l <destinationPath>' or 'ls -la' along the path and unlink it.

Example fix

# before: ~/.claude/agents -> ~/dotfiles/agents
rm ~/.claude/agents && mkdir ~/.claude/agents
# or install into the real location:
planInstallTargetScaffold({ targetRoot: os.path.expanduser('~/dotfiles/claude') })
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
const path = require('path');
function hasSymlinkSegment(dest, root) {
  const rel = path.relative(path.resolve(root), path.resolve(dest));
  let cur = path.resolve(root);
  for (const seg of rel.split(path.sep)) {
    cur = path.join(cur, seg);
    try { if (fs.lstatSync(cur).isSymbolicLink()) return cur; } catch { /* ENOENT ok */ }
  }
  return null;
}

Try / catch

try {
  await applyInstallPlan(plan);
} catch (e) {
  if (e.message.includes('symlinked path')) {
    const p = e.message.match(/'(.+)'/)?.[1];
    console.error(`Replace symlink ${p} with a real directory, or set targetRoot to the physical path.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Any component of destinationPath between the target root and the file itself is a symlink — e.g. ~/.claude/agents is a symlink to a dotfiles repo, or a subdirectory inside the target root was replaced with a link; applying a plan whose destination traverses such a segment.

Common situations: Users who symlink ~/.claude (or subfolders) into a version-controlled dotfiles directory; target root inside a symlinked project path; a previous tool replaced a directory with a link.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


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

Appendix: source

Thrown at scripts/lib/install/apply.js:266

  if (!operation || typeof operation.destinationPath !== 'string') {
    throw new Error('Refusing to apply install operation: missing destination path.');
  }

  const targetRoot = plan && plan.targetRoot;
  assertWithinTrustedRoot(operation.destinationPath, targetRoot, 'install ECC file');

  const resolvedRoot = path.resolve(targetRoot);
  const resolvedTarget = path.resolve(operation.destinationPath);
  const relativePath = path.relative(resolvedRoot, resolvedTarget);
  const segments = relativePath ? relativePath.split(path.sep) : [];
  for (const segmentIndex of Array.from({ length: segments.length + 1 }, (_value, index) => index)) {
    const currentPath = segmentIndex === 0
      ? resolvedRoot
      : path.join(resolvedRoot, ...segments.slice(0, segmentIndex));
    try {
      const stats = fs.lstatSync(currentPath);
      if (stats.isSymbolicLink()) {
        throw new Error(
          `Refusing to install ECC file through symlinked path: '${currentPath}'.`
        );
      }
    } catch (error) {
      if (error && error.code === 'ENOENT') {
        break;
      }
      throw error;
    }
  }
}

function readPreviousInstallState(plan) {
  if (!fs.existsSync(plan.installStatePath)) {
    return null;
  }
  return readInstallState(plan.installStatePath);
}

View on GitHub (pinned to 8321021c54)