affaan-m/ECC · error

seedPaths entries must stay inside repoRoot: ${entry}

Error message

seedPaths entries must stay inside repoRoot: ${entry}

What it means

Thrown by normalizeSeedPaths() when an entry, after being resolved relative to repoRoot, escapes the repoRoot tree (its path.relative starts with '..') or is itself absolute. The orchestrator copies seed paths from repoRoot into each worktree, so allowing an escape would let a config read or overwrite files outside the repository (path traversal). This is a security guard, not a convenience check.

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 01e15490f0)

Solutions

  1. Keep every seedPaths entry strictly inside repoRoot — use relative paths like '.env.local' or 'config/orchestrator.yaml'.
  2. If you need a file from outside the repo, copy it into the repo first (and let git ignore it) instead of referencing it via seedPaths.
  3. Sanitize user-supplied input: strip leading '/', '~', and '../' segments before adding to seedPaths.
  4. Audit symlinks inside repoRoot — the lexical check does not follow them, so a symlink that points outside is a separate risk.

Example fix

// before
seedPaths: ['../shared/launcher.sh']   // -> seedPaths entries must stay inside repoRoot

// after
// copy the file into the repo first, then reference it
seedPaths: ['scripts/launcher.sh']
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');

function sanitizeSeedPaths(entries, repoRoot) {
  const root = path.resolve(repoRoot);
  const safe = [];
  for (const entry of entries || []) {
    if (typeof entry !== 'string' || entry.trim().length === 0) continue;
    const rel = path.relative(root, path.resolve(root, entry));
    if (rel.startsWith('..') || path.isAbsolute(rel)) continue;  // drop, do not throw
    safe.push(rel.split(path.sep).join('/'));
  }
  return safe;
}

config.seedPaths = sanitizeSeedPaths(config.seedPaths, config.repoRoot);

Type guard

function isInsideRepo(entry, repoRoot) {
  if (typeof entry !== 'string' || entry.trim().length === 0) return false;
  const root = path.resolve(repoRoot);
  const rel = path.relative(root, path.resolve(root, entry));
  return !rel.startsWith('..') && !path.isAbsolute(rel);
}

Try / catch

try {
  buildOrchestrationPlan(config);
} catch (error) {
  if (/seedPaths entries must stay inside repoRoot/.test(error.message)) {
    config.seedPaths = sanitizeSeedPaths(config.seedPaths, config.repoRoot);
    buildOrchestrationPlan(config);
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: config.seedPaths: ['../sibling-repo/config'] ; seedPaths: ['/etc/passwd'] ; seedPaths: ['../../.ssh/id_rsa'] ; seedPaths: ['~/secrets.env'] (the ~ is not expanded, but path.resolve treats it as a relative literal and the resolved path may escape); on Windows, seedPaths: ['C:\\other\\dir'].

Common situations: User points at a shared config that lives one directory up; an absolute path leaks in from an env var; symlinks inside the repo resolve outside (note: the check uses path.relative on the literal, so symlinks can still mask traversal — see also that this guard is lexical); a config generator concatenates user input without sanitization.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/41dfcf7a15bfb04e. Report an issue: GitHub.