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
- Replace the symlinked segment with a real directory (e.g. remove the link, mkdir the real directory) or point targetRoot at the actual directory.
- Install directly into the physical location the symlink points to, passing that as targetRoot.
- Use a non-symlinked install target (or the target's native mechanism) for dotfiles management.
- 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
- Avoid symlinked ~/.claude subdirectories; use the target's native mechanisms (junctions on Windows, mounts, or install into the real path).
- Point targetRoot at the physical directory rather than a path that traverses links.
- Audit the install path with 'namei -l' or lstat checks before running the installer in dotfiles-managed environments.
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
- gate.variant_invalid
- output artifact must be a regular file
- output bundle contains a symlink
- output bundle root must not be a symlink
- output destination must not be a symlink
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)