affaan-m/ECC · error · Error

Terminal process did not start correctly.

Error message

Terminal process did not start correctly.

What it means

launchDetached throws this when the value returned by spawn() is not a usable ChildProcess (missing unref), meaning Node did not produce a real detached process handle. This is an internal invariant check that the process object is malformed rather than a spawn failure.

Solutions

  1. Ensure the spawn implementation passed via dependencies.spawn returns a genuine child_process.ChildProcess (or stub with unref/once).
  2. Update the test mock to `return Object.assign(new EventEmitter(), { unref() {} })`.
  3. Remove any monkey-patching of child_process.spawn that alters its return value in production code.

Example fix

// before
const fakeSpawn = () => null;
launch(plan, { spawn: fakeSpawn });
// after
const fakeSpawn = () => Object.assign(new EventEmitter(), { unref() {} });
launch(plan, { spawn: fakeSpawn });
Defensive patterns

Strategy: type-guard

Validate before calling

const spawnImpl = dependencies.spawn || require('child_process').spawn;
if (typeof spawnImpl !== 'function') throw new Error('dependencies.spawn must be a function');

Type guard

function isChildProcess(c) {
  return !!c && typeof c.unref === 'function' && typeof c.once === 'function';
}

Try / catch

try {
  launch(plan, { spawn: mySpawn });
} catch (err) {
  if (err.message === 'Terminal process did not start correctly.') {
    console.error('spawn dependency returned a non-ChildProcess; fix the stub/mock.');
  } else throw err;
}

Prevention

When it happens

Trigger: A dependency-injected/spawned implementation (dependencies.spawn) returns null, undefined, or an object lacking unref — e.g. a test stub or mocked spawn — after the try/catch around the real spawn succeeded.

Common situations: Custom dependency injection where a fake spawn returns a plain object; monkey-patching child_process.spawn in tests; unusual Node runtimes/embeddings whose spawn return differs from the documented ChildProcess API.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/382b1f2e1e0f1c8a. Report an issue: GitHub.

Appendix: 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 8321021c54)