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
- Keep every seedPaths entry strictly inside repoRoot — use relative paths like '.env.local' or 'config/orchestrator.yaml'.
- 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.
- Sanitize user-supplied input: strip leading '/', '~', and '../' segments before adding to seedPaths.
- 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
- Never construct seedPaths from user input without sanitization — this is a path-traversal guard.
- Audit symlinks inside repoRoot: the check is lexical, a symlink pointing outside is still dangerous.
- Prefer relative paths; reject absolute paths and any segment that starts with '..'.
- Unit-test the sanitizer with adversarial inputs ('../', '~/', '/etc/...', nested '..').
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
- TypeScript compiler not found. Install root dev dependencies
- ${source} is missing the expected catalog marker
- Invalid ECC repo root: missing install script at ${installAp
- Missing value for --target
- Unknown catalog command: ${options.command}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/41dfcf7a15bfb04e.
Report an issue: GitHub.