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

  1. Read the referenced server.log (ECC_PLAN_CANVAS_STATE_DIR/server.log) for the child's startup error.
  2. Free the port or pick another via ECC_PLAN_CANVAS_PORT; stop a stale server with `node scripts/plan-canvas.js stop`.
  3. Check the state directory is writable and not corrupted; delete it to reset if needed.
  4. 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

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


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