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

  1. Replace the symlinked parent directory with a real directory (copy contents back) or point the destination inside the real target location.
  2. Verify which path component is the problem with `ls -la` along the destination path and remove/recreate it as a genuine directory.
  3. Update the install-state destinationPath to a location that does not traverse any symlinks under the trusted root.
  4. 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

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


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)