affaan-m/ECC · error · Error
plan-canvas server did not become healthy on port ${port}; c
Error message
plan-canvas server did not become healthy on port ${port}; check ${path.join(stateDir, 'server.log')} What it means
Thrown by ensureServer in scripts/plan-canvas.js after the detached canvas server is spawned and fails the /health check for 50 consecutive polls (100ms apart, ~5s total). The message points at server.log in the state directory so you can read the child's stderr/stdout. This is an infrastructure error, not an argument error.
Source
Thrown at scripts/plan-canvas.js:194
if (health && health.version === VERSION) return port;
if (health) {
await request(port, 'POST', '/shutdown').catch(() => {});
for (let i = 0; i < 20 && (await healthCheck(port)); i++) await sleep(100);
}
fs.mkdirSync(stateDir, { recursive: true });
const logFd = fs.openSync(path.join(stateDir, 'server.log'), 'a');
const child = spawn(process.execPath, [__filename, 'server', '--port', String(port)], {
detached: true,
stdio: ['ignore', logFd, logFd],
env: { ...process.env, ECC_PLAN_CANVAS_STATE_DIR: stateDir }
});
child.unref();
fs.closeSync(logFd);
for (let i = 0; i < 50; i++) {
await sleep(100);
if (await healthCheck(port)) return port;
}
throw new Error(`plan-canvas server did not become healthy on port ${port}; check ${path.join(stateDir, 'server.log')}`);
}
function openBrowser(url) {
const platform = process.platform;
const [cmd, args] =
platform === 'darwin' ? ['open', [url]]
: platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]
: ['xdg-open', [url]];
try {
spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
return true;
} catch {
return false;
}
}
function output(payload) {
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);View on GitHub (pinned to 01e15490f0)
Solutions
- Read the referenced server.log (ECC_PLAN_CANVAS_STATE_DIR/server.log) for the child's startup error.
- Free the port or pick another via ECC_PLAN_CANVAS_PORT; stop a stale server with `node scripts/plan-canvas.js stop`.
- Check the state directory is writable and not corrupted; delete it to reset if needed.
- Verify the Node runtime meets the project's version requirement.
Defensive patterns
Strategy: try-catch
Validate before calling
async function ensureServerOrDiagnose({ stateDir, port }) {
try {
return await ensureServer({ stateDir, port });
} catch (err) {
const log = path.join(stateDir, 'server.log');
const tail = fs.existsSync(log) ? fs.readFileSync(log, 'utf8').split('\n').slice(-20).join('\n') : '(no server.log)';
throw new Error(`${err.message}\n--- server.log tail ---\n${tail}`);
}
} Try / catch
try {
await ensureServer({ stateDir, port });
} catch (err) {
if (/did not become healthy/.test(err.message)) {
const log = fs.readFileSync(path.join(stateDir, 'server.log'), 'utf8');
console.error('Canvas server failed to start. server.log:\n', log.split('\n').slice(-30).join('\n'));
await cleanupStaleServer(stateDir);
}
throw err;
} Prevention
- Always surface server.log when this fires — the child's startup error is recorded there.
- Stop stale servers (`plan-canvas stop`) before retrying so the port is free.
- Pin a Node version that meets ECC's requirements in CI.
When it happens
Trigger: The spawned `node scripts/plan-canvas.js server` process crashes on startup (missing dependency, port already in use, permissions on state dir, Node version too old). The parent retries health for ~5s then gives up. A version mismatch triggers a restart first; this throw means the restart never came up.
Common situations: Another process holds the configured port; the state directory is not writable; a corrupt session store on disk throws during server boot; a recently pulled ECC update requires a newer Node version; the lib/plan-canvas/server module failed to load.
Related errors
- open failed (HTTP ${res.statusCode})
- HTTP ${res.status}
- invalid plan-canvas server port: ${port}
- plan-canvas request path must be root-relative
- plan-canvas request path must stay on the loopback server
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/2f162ae2e9047730.
Report an issue: GitHub.