affaan-m/ECC · error · Error

seedPaths entries must stay inside repoRoot

Error message

seedPaths entries must stay inside repoRoot: ${entry}

What it means

normalizeSeedPaths resolves each seed path against the repo root and rejects entries that escape it (relative path starting with '..' or resolving to an absolute path outside the root). This is a path-traversal guard ensuring seed data can only be copied from within the repository.

Solutions

  1. Change the entry to a path relative to and inside repoRoot (e.g. 'skills/foo', 'scripts/lib').
  2. Copy the needed files into the repo first, then reference them via seedPaths.
  3. If the source must live outside, move it into the repo or create a symlink inside the repo that resolves within the root.
  4. Check for symlinks resolving outside the root and re-point them within the repo.

Example fix

// before
seedPaths: ['../shared/skills']
// after
seedPaths: ['skills'] // after copying shared skills into the repo
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function isInsideRepo(repoRoot, entry) {
  const rel = path.relative(path.resolve(repoRoot), path.resolve(repoRoot, entry));
  return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
}

Type guard

const safeSeedEntry = (e, root) => typeof e === 'string' && e.length > 0 && !path.relative(path.resolve(root), path.resolve(root, e)).startsWith('..') && !path.isAbsolute(path.relative(path.resolve(root), path.resolve(root, e)));

Try / catch

try {
  const seeds = normalizeSeedPaths(repoRoot, entries);
} catch (e) {
  if (String(e.message).startsWith('seedPaths entries must stay inside repoRoot')) {
    console.error('Offending entry:', e.message.split(': ').pop());
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing seedPaths entries like '../shared/config' or absolute paths outside the repo (e.g. '/etc/passwd'); symlinks whose resolution lands outside repoRoot; a config with OS-specific paths built on a different machine.

Common situations: Reusing a seed-paths config across repos where the referenced sibling directory lives outside the current root; attempts to seed from a global folder; symlinked entries in config pointing to a home directory.

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

Appendix: source

Thrown at scripts/lib/tmux-worktree-orchestrator.js:71

function normalizeSeedPaths(seedPaths, repoRoot) {
  const resolvedRepoRoot = path.resolve(repoRoot);
  const entries = Array.isArray(seedPaths) ? seedPaths : [];
  const seen = new Set();
  const normalized = [];

  for (const entry of entries) {
    if (typeof entry !== 'string' || entry.trim().length === 0) {
      continue;
    }

    const absolutePath = path.resolve(resolvedRepoRoot, entry);
    const relativePath = path.relative(resolvedRepoRoot, absolutePath);

    if (
      relativePath.startsWith('..') ||
      path.isAbsolute(relativePath)
    ) {
      throw new Error(`seedPaths entries must stay inside repoRoot: ${entry}`);
    }

    const normalizedPath = relativePath.split(path.sep).join('/');
    if (seen.has(normalizedPath)) {
      continue;
    }

    seen.add(normalizedPath);
    normalized.push(normalizedPath);
  }

  return normalized;
}

function overlaySeedPaths({ repoRoot, seedPaths, worktreePath }) {
  const normalizedSeedPaths = normalizeSeedPaths(seedPaths, repoRoot);

  for (const seedPath of normalizedSeedPaths) {

View on GitHub (pinned to 8321021c54)