affaan-m/ECC · error · Error

Invalid ECC repo root: missing install script at ${installAp

Error message

Invalid ECC repo root: missing install script at ${installApplyPath}

What it means

Every element of the changedFiles array must be a string path. A non-string entry (e.g. { path: '...' }) would stringify to '[object Object]' when joined into the security-trigger haystack, silently poisoning the security scan with garbage. The gate rejects the whole payload rather than shipping a corrupted haystack.

Source

Thrown at scripts/auto-update.js:140

}

// Recognized ECC package names. A repo root is only trusted to run its
// install-apply.js if its package.json identifies it as ECC — otherwise a
// cloned project that ships a nested `evil/{package.json,scripts/install-apply.js}`
// could drive auto-update into executing attacker code (GHSA-hfpv-w6mp-5g95).
const ECC_PACKAGE_NAMES = new Set(['ecc-universal', 'everything-claude-code']);

function validateRepoRoot(repoRoot) {
  const normalized = path.resolve(repoRoot);
  const packageJsonPath = path.join(normalized, 'package.json');
  const installApplyPath = path.join(normalized, 'scripts', 'install-apply.js');

  if (!fs.existsSync(packageJsonPath)) {
    throw new Error(`Invalid ECC repo root: missing package.json at ${packageJsonPath}`);
  }

  if (!fs.existsSync(installApplyPath)) {
    throw new Error(`Invalid ECC repo root: missing install script at ${installApplyPath}`);
  }

  let pkgName = null;
  try {
    pkgName = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).name;
  } catch {
    throw new Error(`Invalid ECC repo root: unreadable package.json at ${packageJsonPath}`);
  }
  if (!ECC_PACKAGE_NAMES.has(pkgName)) {
    throw new Error(`Refusing to run install from untrusted repo root ${normalized}: package.json name '${pkgName}' is not an official ECC package.`);
  }

  return normalized;
}

function runExternalCommand(command, args, options = {}) {
  const result = spawnSync(command, args, {
    cwd: options.cwd,

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Map objects to their path string before passing: changedFiles: files.map(f => f.path).
  2. Filter out nullish entries: changedFiles: files.filter(Boolean).
  3. Add a guard: if (!changedFiles.every(f => typeof f === 'string')) throw in your caller first.

Example fix

// before
orchReview({ diff, changedFiles: files }); // files = [{ path: 'a.js' }, ...]

// after
orchReview({ diff, changedFiles: files.map(f => f.path).filter(Boolean) });
Defensive patterns

Strategy: type-guard

Validate before calling

// Map file objects to string paths and filter nulls before calling.
const changedFiles = rawFiles
  .map(f => (typeof f === 'string' ? f : f?.path))
  .filter(f => typeof f === 'string' && f.length > 0);
orchReview({ diff, changedFiles });

Type guard

function isStringArray(v) {
  return Array.isArray(v) && v.every(x => typeof x === 'string');
}

Prevention

When it happens

Trigger: Passing changedFiles: [{ path: 'a.js' }, { path: 'b.js' }] (objects); changedFiles: ['a.js', 42, null] (mixed types); a .map step that returns the original object instead of the path field.

Common situations: Caller maps a list of file objects from git or an API and forgets to extract the string path; a null/undefined slips into the array from an optional field.

Related errors


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