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
- Read error.cause for the underlying errno (ENOENT/EACCES/EPERM) and fix the specific cause.
- Verify the terminal command exists: `command -v <terminal>` or run with --detect.
- Confirm plan.cwd points to an existing, accessible directory.
- 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
- Verify the terminal binary with --detect or `command -v` before launching in automation.
- Keep the configured cwd an existing directory; resolve it to an absolute path early.
- Log err.cause so the underlying errno (ENOENT/EACCES/EPERM) is visible.
- Pin the terminal name per-platform in config rather than one global default.
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
- ${capability.reason}: ${capability.action}
- Codex probe failed ` : ''}
- Invalid terminal name; use a simple adapter name such as…
- Path is not a valid directory
- Terminal process did not start correctly.
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)