{"record":{"id":"8edd4411b9d1e229","repo":"different-ai/openwork","slug":"timed-out-waiting-for-server-health","errorCode":null,"errorMessage":"Timed out waiting for server health","messagePattern":"Timed out waiting for server health","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/app/lib/opencode.ts","lineNumber":353,"sourceCode":"  const pollMs = options?.pollMs ?? 250;\n\n  const start = Date.now();\n  let lastError: string | null = null;\n\n  while (Date.now() - start < timeoutMs) {\n    try {\n      const health = unwrap(await client.global.health());\n      if (health.healthy) {\n        return health;\n      }\n      lastError = \"Server reported unhealthy\";\n    } catch (error) {\n      lastError = error instanceof Error ? error.message : \"Unknown error\";\n    }\n    await new Promise((resolve) => setTimeout(resolve, pollMs));\n  }\n\n  throw new Error(lastError ?? \"Timed out waiting for server health\");\n}\n","sourceCodeStart":335,"sourceCodeEnd":355,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/app/lib/opencode.ts#L335-L355","documentation":"waitForHealthy polls the opencode server health endpoint until it responds or a deadline expires. Each poll failure's message is retained in lastError; when the loop exhausts, it throws the last seen error, or \"Timed out waiting for server health\" if none was captured. Indicates the server never became reachable in time.","triggerScenarios":"Spawning/starting the local opencode server and polling health until the deadline passes: server binary failed to start, wrong port, crash on boot, or startup slower than the wait budget.","commonSituations":"Missing/incompatible opencode server binary, port already in use or firewalled, machine under load slowing startup, first install downloading dependencies while health polls expire.","solutions":["Check server stderr/stdout logs captured during spawn for a startup crash.","Verify the configured port/host matches where the server actually listens.","Increase the health-wait deadline or poll interval for slower machines.","Confirm the server binary runs standalone (`opencode serve`) to isolate spawn problems."],"exampleFix":"// before: fixed budget\nawait waitForHealthy(server, { timeoutMs: 5000 });\n// after: larger budget on cold start\nawait waitForHealthy(server, { timeoutMs: process.env.CI ? 30000 : 10000 });","handlingStrategy":"retry","validationCode":"// probe before relying on waitForHealthy's deadline\nconst reachable = await fetch(`http://${host}:${port}/health`).then(r => r.ok).catch(() => false);\nif (!reachable) console.warn(\"Server port not reachable; startup may have failed.\");","typeGuard":null,"tryCatchPattern":"try {\n  await waitForHealthy(server, { timeoutMs: 15000 });\n} catch (err) {\n  if (err instanceof Error && /health|Unknown error/i.test(err.message)) {\n    // inspect spawned process logs and exit code before giving up\n    const logs = server.getStderr?.() ?? \"\";\n    throw new Error(`Server failed to become healthy: ${err.message}\\nstderr: ${logs.slice(-500)}`);\n  }\n  throw err;\n}","preventionTips":["Verify the server binary starts standalone before wiring health waits.","Allocate longer health budgets in CI and on cold machine starts.","Check for port conflicts before spawning the server.","Capture and surface server stderr alongside the timeout error."],"tags":["network","startup","health-check","timeout"],"backgroundTag":"health-check-timeout","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}