affaan-m/ECC · error

The local Itô CLI could not be started: ${result.error.messa

Error message

The local Itô CLI could not be started: ${result.error.message}

What it means

Thrown by invokeIto at scripts/ito.js:287-289 after spawnSync returns with result.error truthy. spawnSync populates error (an ENOENT/EACCES/EMFILE/etc. Node error object) when the child could not be spawned at all — distinct from a child that spawned and then exited non-zero (which returns a numeric result.status and is returned, not thrown). The thrown message interpolates result.error.message verbatim. spawnSync was called with shell:false, env filtered by createSafeItoInvocationEnvironment, and process.execPath as the executable (ito.js:261-264), so the spawn target is the Node binary itself with the ito entry as argv[1].

Source

Thrown at scripts/ito.js:288

  const isNodeQualification = command === "evals";
  const isDeviceLogin = command === "login";
  const result = spawnSync(invocation.executable, invocation.args, {
    cwd: process.cwd(),
    encoding: "utf8",
    // Keep policy helpers immutable for callers, but give child-process
    // instrumentation its own mutable copy (for example NODE_V8_COVERAGE).
    env: { ...createSafeItoInvocationEnvironment(environment, args) },
    stdio: isDeviceLogin ? "inherit" : ["pipe", "pipe", "pipe"],
    maxBuffer: MAX_OUTPUT_BYTES,
    timeout: isNodeQualification ? NODE_QUALIFICATION_TIMEOUT_MS : undefined,
    shell: false,
    windowsHide: true,
  });

  if (result.stdout) process.stdout.write(result.stdout);
  if (result.stderr) process.stderr.write(result.stderr);
  if (result.error) {
    throw new Error(`The local Itô CLI could not be started: ${result.error.message}`);
  }
  if (typeof result.status === "number") return result.status;
  if (result.signal) {
    throw new Error(`The local Itô CLI terminated by signal ${result.signal}.`);
  }
  return 1;
}

function main(argv = process.argv.slice(2), environment = process.env) {
  try {
    const parsed = parseArgs(argv, environment);
    if (parsed.help) {
      showHelp();
      return 0;
    }
    const executable = resolveItoExecutable(environment);
    return invokeIto(executable, parsed.invocationArgs, environment);
  } catch (error) {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Read the underlying syscall error in the message — ENOENT, EACCES, EMFILE each have distinct fixes.
  2. For EACCES: `ls -ld` each parent of ECC_ITO_CLI_EXECUTABLE and ensure traverse (x) bit.
  3. For EMFILE: raise the open-file limit (`ulimit -n 4096` or systemd LimitNOFILE).
  4. For ENOENT on Node itself: confirm `process.execPath` is valid (`node -e 'console.log(process.execPath)'`) and the binary still exists.
  5. Re-run with ECC_ITO_CLI_EXECUTABLE pointing at a freshly rebuilt entry.
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require('fs');
function preflightSpawnConditions(executable) {
  // verify node binary is reachable
  if (!fs.existsSync(process.execPath)) throw new Error(`node binary missing: ${process.execPath}`);
  // verify fd budget
  // (node -e 'console.log(process)') would not help; rely on ulimit -n in shell
  // verify parent dir is traversable
  const dir = path.dirname(fs.realpathSync.native(executable));
  fs.accessSync(dir, fs.constants.X_OK);
}

Try / catch

try {
  return invokeIto(executable, args, env);
} catch (err) {
  if (err.message.startsWith('The local Itô CLI could not be started:')) {
    const underlying = err.message.split(':').slice(1).join(':').trim();
    if (underlying.includes('ENOENT')) { /* node binary or script gone */ }
    else if (underlying.includes('EACCES')) { /* permissions */ }
    else if (underlying.includes('EMFILE') || underlying.includes('ENFILE')) { /* raise ulimit */ }
  }
  throw err;
}

Prevention

When it happens

Trigger: process.execPath (the running Node binary) cannot spawn the resolved ito.js: most commonly ENOENT on the node binary path itself, EACCES on the ito entry's parent dir, EMFILE/ENOMEM under fd or memory exhaustion, or EAGAIN under fork limits. The thrown message will include the underlying syscall error.

Common situations: Container with ulimit -n too low (EMFILE); CI runner out of memory; the ito entry's parent dir lost traverse permission after ECC_ITO_CLI_EXECUTABLE was validated; the Node binary was replaced/moved while ECC was running; SELinux/AppArmor denying exec.

Related errors


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