affaan-m/ECC · error
Refusing to : destination parent is not a trusted directory.
Error message
Refusing to ${action}: destination parent is not a trusted directory. What it means
ensureContainedWriteDestination walks each parent directory segment between the trusted root and the destination, verifying via lstat that every intermediate component is a real directory and not a symbolic link. If a component is a symlink or otherwise not a directory, the write is refused to prevent symlink-based escapes from the trusted root (defense against malicious install state).
Solutions
- Replace the symlinked parent directory with a real directory (copy contents back) or point the destination inside the real target location.
- Verify which path component is the problem with `ls -la` along the destination path and remove/recreate it as a genuine directory.
- Update the install-state destinationPath to a location that does not traverse any symlinks under the trusted root.
- Re-run the install after fixing the filesystem layout; if a tool keeps recreating the symlink, reconfigure that tool.
Example fix
# before: ~/.claude/agents -> ~/dotfiles/agents (symlink) # after rm ~/.claude/agents mkdir ~/.claude/agents cp -r ~/dotfiles/agents/. ~/.claude/agents/
Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
function assertNoSymlinkParents(destPath, root) {
let cur = require('path').dirname(require('path').resolve(destPath));
const stop = require('path').resolve(root);
while (cur.startsWith(stop)) {
const st = fs.lstatSync(cur);
if (st.isSymbolicLink() || !st.isDirectory()) {
throw new Error(`Parent is a symlink or not a directory: ${cur}`);
}
const parent = require('path').dirname(cur);
if (parent === cur) break;
cur = parent;
}
} Type guard
function isRealDirectory(p) {
try { const st = fs.lstatSync(p); return st.isDirectory() && !st.isSymbolicLink(); } catch { return false; }
} Try / catch
try {
await install(operations);
} catch (err) {
if (/destination parent is not a trusted directory/.test(err.message)) {
console.error('Replace the symlinked parent directory with a real directory, then re-run.');
} else throw err;
} Prevention
- Do not symlink ~/.claude or its subdirectories into dotfiles repos; use real directories and a dotfiles manager that copies.
- Exclude the install directories from sync tools that create junctions/symlinks.
- Check `ls -la` along the destination path before installing on a new machine.
When it happens
Trigger: The destination path contains a parent component that exists on disk as a symbolic link, or as a regular file, while preparing a contained write — e.g. ~/.claude/agents being a symlink to another location, or a path segment replaced by a file during a concurrent operation.
Common situations: Users symlink ~/.claude (or a subdirectory like agents/, skills/, hooks/) into a dotfiles repo or another volume; sync tools (Dropbox, OneDrive) replacing directories with junctions/symlinks; an attacker or corrupted state swapping a directory component for a symlink mid-install.
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
- Invalid ECC repo root: missing package.json at
- Nasiko executable must be a regular file, not a symlink.
- Nasiko install directory must be a real directory, not a…
- output artifact must be a regular file
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/66d56448fc768481.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install-lifecycle.js:403
? relativeParent.split(path.sep).filter(Boolean)
: [];
let currentPath = canonicalRoot;
for (const segment of pathSegments) {
const validatedParent = assertWithinTrustedRoot(currentPath, canonicalRoot, action);
const nextPath = path.join(validatedParent, segment);
try {
fs.mkdirSync(nextPath);
} catch (error) {
if (!error || error.code !== 'EEXIST') {
throw error;
}
}
const validatedNext = assertWithinTrustedRoot(nextPath, canonicalRoot, action);
const nextStat = fs.lstatSync(validatedNext);
if (!nextStat.isDirectory() || nextStat.isSymbolicLink()) {
throw new Error(`Refusing to ${action}: destination parent is not a trusted directory.`);
}
currentPath = validatedNext;
}
return getManagedDestination(managedPath, canonicalRoot, action).managedPath;
}
function prepareContainedWriteDestination(destinationPath, trustedRoot, action) {
return ensureContainedParentDir(destinationPath, trustedRoot, action);
}
function getContainedExistingPath(
destinationPath,
trustedRoot,
action,
{ allowFinalSymlink = false } = {}
) {
const initialDestination = getManagedDestination(View on GitHub (pinned to 8321021c54)