affaan-m/ECC · error · Error

Terminal process did not start correctly.

Error message

Terminal process did not start correctly.

What it means

After spawn returns, launchDetached verifies the returned child object has an unref function (needed to detach the process from the parent's event loop). If spawnImpl returned null/undefined or a non-Child object, this throws. It is primarily a guard against a non-conformant spawn in the dependency-injection seam rather than a normal runtime condition.

Source

Thrown at skills/terminal-opener/scripts/open-terminal.js:271

function reportDetachedError(error) {
  process.stderr.write(`Error: ${error.message}\n`);
  process.exitCode = 1;
}

function launchDetached(command, args, cwd, spawnImpl, onDetachedError) {
  let child;
  try {
    child = spawnImpl(command, args, {
      cwd,
      detached: true,
      shell: false,
      stdio: 'ignore',
    });
  } catch (error) {
    throw new Error(`Unable to start ${command}: ${error.message}`, { cause: error });
  }
  if (!child || typeof child.unref !== 'function') {
    throw new Error('Terminal process did not start correctly.');
  }
  if (typeof child.once === 'function') {
    child.once('error', error => {
      onDetachedError(
        new Error(`Unable to start ${command}: ${error.message}`, { cause: error })
      );
    });
  }
  child.unref();
}

function launch(plan, dependencies = {}) {
  const spawnSyncImpl = dependencies.spawnSync || childProcess.spawnSync;
  const spawnImpl = dependencies.spawn || childProcess.spawn;
  const onDetachedError = dependencies.onDetachedError || reportDetachedError;
  const capability = detectTerminalCapability(plan, spawnSyncImpl);
  if (!capability.available) {
    throw new Error(`${capability.reason}: ${capability.action}`);

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Use the default child_process.spawn, or ensure an injected spawnImpl returns a real ChildProcess with unref().
  2. In tests, have the spawn double return an object exposing unref() and once().

Example fix

// before (test double returns nothing)
const fakeSpawn = () => undefined;
launch(plan, { spawn: fakeSpawn });

// after
const fakeSpawn = () => ({ unref() {}, once() {} });
launch(plan, { spawn: fakeSpawn });
Defensive patterns

Strategy: type-guard

Type guard

function isChildProcess(value) {
  return !!value && typeof value.unref === 'function' && typeof value.once === 'function';
}
// when injecting a spawn double into launch():
const spawnImpl = (cmd, args, opts) => {
  const child = realSpawn(cmd, args, opts);
  if (!isChildProcess(child)) throw new Error('spawn did not return a ChildProcess');
  return child;
};

Prevention

When it happens

Trigger: spawnImpl returns null/undefined or an object lacking unref (e.g., a test double, mock, or a custom spawn shim with the wrong return shape).

Common situations: Unit tests passing a fake spawn that returns {} or undefined; a custom launch() dependency injection with a broken spawnImpl; an extremely old/abnormal Node environment.

Related errors


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