affaan-m/ECC · critical · Error
Refusing to install ECC file through symlinked path: '${curr
Error message
Refusing to install ECC file through symlinked path: '${currentPath}'. What it means
Thrown by assertSafeInstallOperation in scripts/lib/install/apply.js during the segment walk. After asserting the destination is contained within targetRoot via assertWithinTrustedRoot, the guard walks every path segment from targetRoot down to the destination and lstatSyncs each. If any segment is a symbolic link, the install aborts. This blocks symlink-escape attacks where a directory under the trusted root points elsewhere on disk; it also narrows (but cannot fully eliminate) the TOCTOU window around mkdirSync, as noted in the source comment.
Source
Thrown at scripts/lib/install/apply.js:215
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 buildResolvedClaudeHooks(plan) {
if (!plan.adapter || (plan.adapter.target !== 'claude' && plan.adapter.target !== 'claude-project')) {
return null;
}
const pluginRoot = plan.targetRoot;View on GitHub (pinned to 01e15490f0)
Solutions
- Locate the symlink with find <targetRoot> -type l -ls and inspect each result.
- Replace the symlink with a real directory: copy contents into place, then remove the symlink.
- Re-run the install. If you need cross-machine sync, sync the directory contents rather than the directory itself.
- On macOS also check ~/Library/Application Support for app-managed symlinks.
Example fix
# before ~/.claude/skills -> /Users/me/Dropbox/skills # after rm ~/.claude/skills mkdir ~/.claude/skills rsync -a /Users/me/Dropbox/skills/ ~/.claude/skills/
Defensive patterns
Strategy: validation
Validate before calling
function assertNoSymlinksUnder(rootDir) {
const stack = [rootDir];
while (stack.length) {
const cur = stack.pop();
let s;
try { s = fs.lstatSync(cur); } catch (e) { if (e.code === 'ENOENT') continue; throw e; }
if (s.isSymbolicLink()) throw new Error(`Symlink in install path: ${cur}`);
if (s.isDirectory()) {
for (const entry of fs.readdirSync(cur)) stack.push(path.join(cur, entry));
}
}
}
assertNoSymlinksUnder(targetRoot); Try / catch
try {
applyInstallPlan(plan);
} catch (err) {
if (/through symlinked path/.test(err.message)) {
console.error('Remove symlinks under', plan.targetRoot, 'then retry.');
}
throw err;
} Prevention
- Keep ECC install targets (e.g. ~/.claude) as real directories, not symlinks.
- Audit symlinks before install: find <root> -type l -ls.
- Do not clone untrusted repos and install into them without inspection.
- Prefer content-syncing (rsync) over directory-symlinking for dotfile management.
When it happens
Trigger: Any component of the resolved destination path — from targetRoot down — is a symbolic link at the moment of the check. E.g. ~/.claude/skills -> ~/Dropbox/skills, or a compromised project where .cursor/skills is symlinked to /etc.
Common situations: Users who sync ~/.claude across machines via Dropbox/iCloud (which create symlinks); monorepo workspaces where the target dir is a symlinked shared path; dotfile managers that symlink ~/.claude; a maliciously crafted cloned repo (security-relevant, GHSA-class).
Related errors
- Refusing to ${action}: destination parent is not a trusted d
- Refusing to ${action} through symlinked Claude skill path: '
- Refusing to access memory through symlink root: ${root}
- Refusing to access memory through symlink directory: ${direc
- Refusing to read non-file path: ${filePath}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/83a75456af74fca1.
Report an issue: GitHub.