affaan-m/ECC · error · GateError

gate.variant_missing

gate.variant_missing

Error message

variant.json missing in ${resolved}

What it means

loadVariant resolves the variant directory, looks for variant.json inside it, and when that file does not exist (ENOENT) throws GateError('gate.variant_missing') including the resolved directory. Every variant capsule must contain a variant.json manifest describing name, effect_class, and entry.

Solutions

  1. Confirm the directory contains variant.json (`ls <dir>/variant.json`); create or regenerate it if missing.
  2. Check the path passed to loadVariant points at the variant directory itself, not its parent or the capsule root.
  3. Regenerate the variant from the capsule source so the manifest is emitted.
  4. Run the gate from the expected working directory or pass an absolute path.

Example fix

// before
loadVariant('./variants')            // parent dir, no manifest inside
// after
loadVariant('./variants/baseline')   // contains variant.json
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require('fs'), path = require('path');
function variantManifestExists(dir) {
  return fs.existsSync(path.join(path.resolve(dir), 'variant.json'));
}
if (!variantManifestExists(dir)) throw new Error(`variant.json missing in ${path.resolve(dir)}`);

Type guard

function isDirWithManifest(p) {
  try {
    return require('fs').statSync(p).isDirectory() &&
      require('fs').statSync(require('path').join(p, 'variant.json')).isFile();
  } catch { return false; }
}

Try / catch

try {
  const variant = loadVariant(dir);
} catch (e) {
  if (e.code === 'gate.variant_missing') {
    console.error(`${e.message} — regenerate the variant or fix the path`);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling loadVariant on a directory without variant.json; passing the wrong directory (e.g. the capsule root or a parent); the manifest was deleted, renamed, or never generated.

Common situations: Typo'd path to the variant dir; a fresh clone where variants were never generated; a script that writes manifest to a different filename (variants.json, manifest.json); running the gate from the wrong working directory with a relative path.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/8dff63dd0874b903. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/eval-harness/gate.js:116

function digestDir(dir) {
  const hash = crypto.createHash('sha256');
  for (const relative of listFiles(dir)) {
    hash.update(relative);
    hash.update('\0');
    hash.update(readRegularFile(path.join(dir, relative)));
    hash.update('\0');
  }
  return hash.digest('hex');
}

function loadVariant(dir) {
  const resolved = fs.realpathSync(path.resolve(dir));
  const manifestPath = path.join(resolved, 'variant.json');
  let manifestBytes;
  try {
    manifestBytes = readRegularFile(manifestPath, 'utf8');
  } catch (error) {
    if (error.code === 'ENOENT') throw new GateError('gate.variant_missing', `variant.json missing in ${resolved}`);
    throw error;
  }
  const manifest = JSON.parse(manifestBytes);
  if (typeof manifest.name !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(manifest.name) || !envelope.EFFECT_CLASSES.includes(manifest.effect_class)) {
    throw new GateError('gate.variant_invalid', `variant.json in ${resolved} needs name and a valid effect_class`);
  }
  const entry = manifest.entry === undefined ? 'run.js' : manifest.entry;
  if (typeof entry !== 'string' || !entry || path.isAbsolute(entry) || path.win32.isAbsolute(entry) || entry.includes('\\') || entry.split('/').includes('..')) {
    throw new GateError('gate.variant_invalid', 'entry must be a relative regular file within the variant');
  }
  const entryPath = path.resolve(resolved, entry);
  const relative = path.relative(resolved, entryPath);
  if (!relative || relative.startsWith('..' + path.sep) || path.isAbsolute(relative) || !listFiles(resolved).includes(relative.split(path.sep).join('/')) || !fs.lstatSync(entryPath).isFile()) {
    throw new GateError('gate.variant_invalid', 'entry must be covered by the variant digest');
  }
  return { dir: resolved, name: manifest.name, effect_class: manifest.effect_class, entry: relative, digest: digestDir(resolved) };
}

View on GitHub (pinned to 8321021c54)