affaan-m/ECC · error

Refusing to outside the install root: ' ' is not within ' '.

Error message

Refusing to ${action} outside the install root: '${targetPath}' is not within '${targetRoot}'.

What it means

assertSafeSkillPath resolves the target path and verifies it lies strictly inside the install root (not the root itself, not escaping via .., not absolute after normalization) before any skill file operation. This is a path-traversal guard: migration/removal code must never touch files outside the Claude skills root.

Solutions

  1. Check the targetPath value in the error and correct it so it resolves inside the install root.
  2. Verify the install root configuration points at the actual ~/.claude (or equivalent) skills directory.
  3. If the path comes from stored state (previous install records), delete or regenerate that stale state.
  4. Never bypass by passing raw user input; normalize/resolve relative paths against the correct root first.

Example fix

// before
await removeLegacyClaudeSkillFiles(root, '/etc/hosts');
// after
await removeLegacyClaudeSkillFiles(root, path.join(root, 'skills', 'old-skill', 'SKILL.md'));
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function isInsideRoot(root, target) {
  const rel = path.relative(path.resolve(root), path.resolve(target));
  return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
}

Type guard

const isSafeSkillPath = (root, target) => isInsideRoot(root, target);

Try / catch

try {
  await assertSafeClaudeSkillOperation({ action: 'remove', targetPath, targetRoot });
} catch (error) {
  if (error.message.includes('outside the install root')) {
    console.error(`Resolve ${targetPath} under ${targetRoot} before retrying`);
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling assertSafeClaudeSkillOperation (or the skill migration/cleanup helpers) with a targetPath that resolves outside targetRoot — e.g. containing ../ segments, being a symlink target outside the root, or passing an absolute path in another directory.

Common situations: Misconfigured install root (CLAUDE config pointing elsewhere), corrupted stored skill paths containing ../, or passing a user-supplied path from a CLI argument straight into the migration API.

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/d6081183501416b6. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install/claude-skill-migration.js:56

function comparablePath(filePath) {
  const resolvedPath = path.resolve(filePath);
  return process.platform === 'win32' ? resolvedPath.toLowerCase() : resolvedPath;
}

function samePath(leftPath, rightPath) {
  return comparablePath(leftPath) === comparablePath(rightPath);
}

function assertSafeSkillPath(targetPath, targetRoot, action) {
  const resolvedRoot = path.resolve(targetRoot);
  const resolvedTarget = path.resolve(targetPath);
  const relativePath = path.relative(resolvedRoot, resolvedTarget);
  if (
    relativePath === ''
    || relativePath.startsWith('..')
    || path.isAbsolute(relativePath)
  ) {
    throw new Error(
      `Refusing to ${action} outside the install root: '${targetPath}' is not within '${targetRoot}'.`
    );
  }

  let currentPath = resolvedRoot;
  for (const segment of relativePath.split(path.sep)) {
    currentPath = path.join(currentPath, segment);
    let stats;
    try {
      stats = fs.lstatSync(currentPath);
    } catch (error) {
      if (error && error.code === 'ENOENT') {
        break;
      }
      throw error;
    }
    if (stats.isSymbolicLink()) {
      throw new Error(

View on GitHub (pinned to 8321021c54)