{"record":{"id":"2f162ae2e9047730","repo":"affaan-m/ECC","slug":"plan-canvas-server-did-not-become-healthy-on-port","errorCode":null,"errorMessage":"plan-canvas server did not become healthy on port ${port}; check ${path.join(stateDir, 'server.log')}","messagePattern":"plan-canvas server did not become healthy on port (.+?); check (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/plan-canvas.js","lineNumber":194,"sourceCode":"  if (health && health.version === VERSION) return port;\n  if (health) {\n    await request(port, 'POST', '/shutdown').catch(() => {});\n    for (let i = 0; i < 20 && (await healthCheck(port)); i++) await sleep(100);\n  }\n  fs.mkdirSync(stateDir, { recursive: true });\n  const logFd = fs.openSync(path.join(stateDir, 'server.log'), 'a');\n  const child = spawn(process.execPath, [__filename, 'server', '--port', String(port)], {\n    detached: true,\n    stdio: ['ignore', logFd, logFd],\n    env: { ...process.env, ECC_PLAN_CANVAS_STATE_DIR: stateDir }\n  });\n  child.unref();\n  fs.closeSync(logFd);\n  for (let i = 0; i < 50; i++) {\n    await sleep(100);\n    if (await healthCheck(port)) return port;\n  }\n  throw new Error(`plan-canvas server did not become healthy on port ${port}; check ${path.join(stateDir, 'server.log')}`);\n}\n\nfunction openBrowser(url) {\n  const platform = process.platform;\n  const [cmd, args] =\n    platform === 'darwin' ? ['open', [url]]\n      : platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]\n        : ['xdg-open', [url]];\n  try {\n    spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction output(payload) {\n  process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/plan-canvas.js#L176-L212","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"async function ensureServerOrDiagnose({ stateDir, port }) {\n  try {\n    return await ensureServer({ stateDir, port });\n  } catch (err) {\n    const log = path.join(stateDir, 'server.log');\n    const tail = fs.existsSync(log) ? fs.readFileSync(log, 'utf8').split('\\n').slice(-20).join('\\n') : '(no server.log)';\n    throw new Error(`${err.message}\\n--- server.log tail ---\\n${tail}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ensureServer({ stateDir, port });\n} catch (err) {\n  if (/did not become healthy/.test(err.message)) {\n    const log = fs.readFileSync(path.join(stateDir, 'server.log'), 'utf8');\n    console.error('Canvas server failed to start. server.log:\\n', log.split('\\n').slice(-30).join('\\n'));\n    await cleanupStaleServer(stateDir);\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["plan-canvas","network","server","health-check","infrastructure"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}