{"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/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/plan-canvas.js#L176-L212","documentation":"ensureServer spawns the plan-canvas server as a detached child, then polls the health endpoint up to 50 times with 100ms sleeps (about 5s total). If the server never reports healthy within that window — crash on boot, port conflict, missing runtime — this error is thrown, pointing at the server.log file in the state directory for the real cause.","triggerScenarios":"Starting cmdOpen (or any command needing the server) when the spawned process exits immediately (bad state dir, port already bound by another process), when the server binary crashes during init, or when the machine is too slow for the ~5s polling budget.","commonSituations":"Port already in use by a stale server instance; stateDir not writable so server.log/child fails; Node/runtime version mismatch; firewall or sandbox blocking loopback binds; first-run dependency missing.","solutions":["Open the referenced log file (<stateDir>/server.log) — it contains the server's actual startup error.","Check if a stale server already holds the port (`lsof -i :<port>` / `ss -ltnp`) and kill it or pick another port.","Verify the state directory exists and is writable.","Retry once — a transiently slow machine can exceed the 5s health window; if it recurs, increase the poll count/timeout in ensureServer."],"exampleFix":"// before\nPORT=4173 node scripts/plan-canvas.js open plan.html   # fails: stale server on 4173\n// after\nlsof -ti :4173 | xargs kill -9\nPORT=4174 node scripts/plan-canvas.js open plan.html","handlingStrategy":"try-catch","validationCode":"async function isHealthy(port) {\n  try {\n    const res = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(1000) });\n    return res.ok;\n  } catch { return false; }\n}\n// check for a stale/conflicting listener before starting\nconst inUse = await isHealthy(port);","typeGuard":null,"tryCatchPattern":"try {\n  await cmdOpen(file, args, { stateDir, port });\n} catch (err) {\n  if (err.message.includes('did not become healthy')) {\n    const logPath = path.join(stateDir, 'server.log');\n    console.error(`Server failed to start. Tail of ${logPath}:`);\n    console.error(fs.readFileSync(logPath, 'utf8').split('\\n').slice(-20).join('\\n'));\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Check server.log first — it holds the real startup failure","Detect and kill stale listeners on the port before launching","Ensure the state directory exists and is writable before spawning","Allow headless/CI environments extra startup time or increase the poll budget"],"tags":["server","startup","timeout","health-check"],"backgroundTag":"request-timeout","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}