affaan-m/ECC · error · GateError
gate.variant_invalid
gate.variant_invalid
Error message
variant trees must contain only regular files and directories
What it means
listFiles() walks a variant tree and rejects anything that is not a regular file or directory — most importantly symbolic links, but also sockets, FIFOs, and device nodes — with 'gate.variant_invalid'. Symlinks are forbidden because variants are content-addressed by digest; a symlink could escape the tree (path traversal) or make the digest non-reproducible. The same guard backs digestDir, loadVariant, and scanTripwires.
Solutions
- Find the offending entry with: find <variant-dir> -type l -o ! -type f -o ! -type d (excluding node_modules/.git).
- Replace symlinks with real copies of their targets (cp -L) or remove them if unnecessary.
- Delete stray sockets/FIFOs (e.g. leftover *.sock files) from the tree.
- Re-copy the variant tree with dereferencing (rsync -L or cp -rL) so only regular files/dirs remain.
Example fix
// before: variant dir contains a symlink to shared fixtures
const variant = loadVariant('./variants/base'); // gate.variant_invalid
// after: materialize links as real files first
// cp -rL ./variants/base ./variants/base-resolved && rm -rf ./variants/base-resolved/node_modules
const variant = loadVariant('./variants/base-resolved'); Defensive patterns
Strategy: validation
Validate before calling
const { execSync } = require('child_process');
const links = execSync(`find ${dir} -type l -not -path '*/node_modules/*' -not -path '*/.git/*'`).toString().trim();
if (links) throw new Error(`symlinks in variant tree must be resolved first:\n${links}`); Type guard
function isRegularTree(dir) {
for (const e of fs.readdirSync(dir, { withFileTypes: true, recursive: true })) {
if (e.name === 'node_modules' || e.name === '.git') continue;
if (e.isSymbolicLink() || (!e.isFile() && !e.isDirectory())) return false;
}
return true;
} Try / catch
try {
const files = listFiles(variantDir, variantDir, []);
} catch (e) {
if (e instanceof GateError && e.code === 'gate.variant_invalid') {
throw new Error(`${e.message}; fix with: find ${variantDir} -type l -exec cp -L {} {}.real \\; -exec mv {}.real {} \\;`);
}
throw e;
} Prevention
- Materialize variant trees with cp -rL / rsync -L so no symlinks survive.
- Clean stray sockets/FIFOs (*.sock) from directories used as variants.
- Avoid running dev servers inside variant directories.
- Keep only node_modules/.git exclusions in mind — every other non-regular entry fails the gate.
When it happens
Trigger: Calling loadVariant/gate on a variant directory containing a symlink (e.g. node_modules-style links, a symlinked config, or .git worktree links not skipped), a Unix socket left by a dev server, or a named pipe inside the tree.
Common situations: Checking out fixtures via a tool that creates symlinks; copying variant trees with cp -r that preserved links; running a dev server in the variant dir that left a .sock file; pnpm-style symlinked node_modules layouts (only node_modules and .git are skipped, not other link farms).
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
- output artifact must be a regular file
- output bundle contains a symlink
- output bundle root must not be a symlink
- output destination must not be a symlink
- output directory must not be a symlink
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/503924edb097cc4c.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/eval-harness/gate.js:62
]);
class GateError extends Error {
constructor(code, message, details = {}) {
super(message);
this.name = 'GateError';
this.code = code;
Object.assign(this, details);
}
}
function listFiles(dir, base = dir, acc = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
if (entry.name === 'node_modules' || entry.name === '.git') {
continue;
}
const full = path.join(dir, entry.name);
if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) {
throw new GateError('gate.variant_invalid', 'variant trees must contain only regular files and directories');
}
if (entry.isDirectory()) {
listFiles(full, base, acc);
} else if (entry.isFile()) {
acc.push(path.relative(base, full).split(path.sep).join('/'));
}
}
return acc;
}
/** Read the opened regular file, never reopen a previously checked pathname.
* No-follow/nonblocking flags reduce symlink and special-file hazards where
* supported. Descriptor/path identity also rejects symlinks on other hosts.
* This is static inspection of a caller-controlled tree, not OS containment.
*/
function readRegularFile(filePath, encoding) {
const flags = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0) | (fs.constants.O_NONBLOCK || 0);
let fd;View on GitHub (pinned to 8321021c54)