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
- Read the underlying syscall error in the message — ENOENT, EACCES, EMFILE each have distinct fixes.
- For EACCES: `ls -ld` each parent of ECC_ITO_CLI_EXECUTABLE and ensure traverse (x) bit.
- For EMFILE: raise the open-file limit (`ulimit -n 4096` or systemd LimitNOFILE).
- For ENOENT on Node itself: confirm `process.execPath` is valid (`node -e 'console.log(process.execPath)'`) and the binary still exists.
- 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
- Set a healthy ulimit -n in CI (>= 4096).
- Build and run ecc ito as the same user to avoid EACCES on the dist tree.
- Pin the Node version so process.execPath does not disappear mid-pipeline.
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
- The local Itô CLI terminated by signal ${result.signal}.
- ${option} is required exactly once for live node qualificati
- ${option} requires a non-empty value for live node qualifica
- Live node qualification requires ITO_ENABLE_SIXTYTWO_LIVE=1
- Live node qualification requires --live-sixtytwo exactly onc
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/cb1917e45faa8371.
Report an issue: GitHub.