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
- Use the default child_process.spawn, or ensure an injected spawnImpl returns a real ChildProcess with unref().
- 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 injecting a custom spawn into launch(), return a real ChildProcess (with unref and once).
- In tests, model the spawn double after child_process.spawn's return shape, including unref().
- Do not substitute a generic stub for spawn; launchDetached depends on the ChildProcess contract.
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
- Unable to start ${command}: ${error.message}
- ECC_PROJECT_DIR must be an absolute path within /workspace.
- Multiple ECC repo roots detected: ${uniqueRepoRoots.join(',
- README.md is missing the quick-start catalog summary
- Command "${commandName}" terminated by signal ${result.signa
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/382b1f2e1e0f1c8a.
Report an issue: GitHub.