affaan-m/ECC · error

The local Itô CLI terminated by signal ${result.signal}.

Error message

The local Itô CLI terminated by signal ${result.signal}.

What it means

Thrown by invokeIto at scripts/ito.js:291-293 when spawnSync returns no numeric status but a truthy signal — meaning the Itô child was terminated by a signal rather than exiting normally. For the evals subcommand, spawnSync is called with timeout=NODE_QUALIFICATION_TIMEOUT_MS (31 minutes, ito.js:24, 280); when the timeout elapses spawnSync sends the default killSignal (SIGTERM, since killSignal is not overridden) and surfaces it here as result.signal. The thrown message names the signal.

Source

Thrown at scripts/ito.js:292

    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) {
    console.error(`Error: ${error.message}`);
    return 1;
  }
}

View on GitHub (pinned to 01e15490f0)

Solutions

  1. For SIGTERM on evals: investigate node reachability and SSH agent forwarding; rerun with reachable nodes and a healthy sixtytwo-cli (pinned 0.3.33 per the help text).
  2. For SIGKILL: check dmesg / journalctl for OOM kills and increase memory limits.
  3. For SIGINT: the operator interrupted — rerun when ready.
  4. If 31 minutes is genuinely too short for your node count, split the --nodes list into smaller batches and rerun.
Defensive patterns

Strategy: try-catch

Validate before calling

function preflightNodes(nodeList) {
  const nodes = nodeList.split(',').map(s => s.trim()).filter(Boolean);
  if (nodes.length === 0) throw new Error('--nodes empty');
  return nodes;
}
// also surface a clearer warning when the timeout (31m) is likely too short for the node count

Try / catch

try {
  return invokeIto(executable, args, env);
} catch (err) {
  if (err.message.startsWith('The local Itô CLI terminated by signal')) {
    const sig = err.message.match(/signal (\w+)/)[1];
    if (sig === 'SIGTERM') { /* likely the 31m evals timeout; rerun with smaller node batches */ }
    else if (sig === 'SIGKILL') { /* OOM or external kill */ }
  }
  throw err;
}

Prevention

When it happens

Trigger: Most commonly: `ecc ito evals ...` exceeding the 31-minute node-qualification timeout and being SIGTERM'd by spawnSync. Also fires on external signals: OOM killer sending SIGKILL (result.signal === 'SIGKILL'), operator/SIGINT during device login, or container runtime SIGTERM. Less common for non-evals commands because timeout is undefined for them, but external signals can still apply.

Common situations: Node qualification hanging against unreachable sixtytwo nodes; slow SSH agent forwarding; the canonical CLI waiting on a network call that never returns; container killed by orchestrator during a long evals run; manual Ctrl-C propagated to the child.

Related errors


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