affaan-m/ECC · error · Error

Unable to start ${command}: ${error.message}

Error message

Unable to start ${command}: ${error.message}

What it means

launchDetached wraps spawnImpl (child_process.spawn by default) in a try. If spawn throws synchronously — invalid command type, EACCES on the binary, EINVAL options — the error is re-thrown as 'Unable to start <command>: <message>' with the original error attached via { cause }. This is the synchronous spawn-failure path; asynchronous errors go through the child's 'error' event and the onDetachedError callback instead.

Source

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

  };
}

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;

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Inspect error.cause for the original syscall error (EACCES, EINVAL, ENOENT).
  2. Ensure the wezterm binary is executable and on PATH (run --detect first).
  3. If injecting a custom spawnImpl dependency, ensure it returns a ChildProcess and only throws on genuine failures.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  launch(plan);
} catch (error) {
  if (error.message.startsWith('Unable to start') && error.cause) {
    log.error('terminal spawn failed', { code: error.cause.code, message: error.cause.message });
  }
  throw error;
}

Prevention

When it happens

Trigger: spawnImpl(command, args, opts) throws synchronously inside launchDetached. Called from launch() for recover mode (line 293) and for the fallback after a failed mux spawn (line 314).

Common situations: wezterm binary present but not executable (EACCES); command argument of the wrong type; a custom injected spawnImpl that throws; ENOENT-style failures surfacing synchronously on some platforms.

Related errors


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