affaan-m/ECC · error · Error

Unable to start

Error message

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

What it means

launchDetached wraps a synchronous spawn failure (child_process.spawn throwing synchronously) into `Unable to start <command>: <reason>` with the original error attached as `cause`. It means the OS/Node refused to even create the terminal process for the resolved terminal command.

Solutions

  1. Read error.cause for the underlying errno (ENOENT/EACCES/EPERM) and fix the specific cause.
  2. Verify the terminal command exists: `command -v <terminal>` or run with --detect.
  3. Confirm plan.cwd points to an existing, accessible directory.
  4. Reinstall or reconfigure the terminal emulator named in your settings.

Example fix

// before
const child = spawn('kitty', args, { cwd, detached: true }); // kitty not installed
// after
// pick an installed terminal (verify with --detect)
const child = spawn('gnome-terminal', args, { cwd, detached: true });
Defensive patterns

Strategy: try-catch

Validate before calling

const { execSync } = require('child_process');
try { execSync(`command -v ${terminal}`, { stdio: 'ignore' }); } catch { throw new Error(`Terminal '${terminal}' not found on PATH`); }
if (!fs.existsSync(cwd)) throw new Error(`cwd does not exist: ${cwd}`);

Try / catch

try {
  const result = launch(plan);
} catch (err) {
  if (err.message.startsWith('Unable to start')) {
    console.error(`Spawn failed: ${err.cause ? err.cause.message : err.message}`);
    if (err.cause?.code === 'ENOENT') console.error('-> terminal binary not found; run --detect to list terminals.');
    if (err.cause?.code === 'EACCES') console.error('-> binary not executable; fix permissions or pick another terminal.');
  } else throw err;
}

Prevention

When it happens

Trigger: spawn() throws synchronously for the detached child — e.g. the terminal binary path does not exist or lacks execute permission, an invalid cwd option, or an options object Node rejects — reaching the catch block around childProcess.spawn.

Common situations: Terminal emulator uninstalled or renamed after configuration (e.g. iterm2 config on Linux); cwd directory deleted before launch; running under a restricted environment where spawning detached processes is denied; ENOENT/EPERM/EACCES from the OS.

Related errors


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

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