different-ai/openwork · error
Timed out waiting for server health
Error message
Timed out waiting for server health
What it means
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.
Source
Thrown at apps/app/src/app/lib/opencode.ts:353
const pollMs = options?.pollMs ?? 250;
const start = Date.now();
let lastError: string | null = null;
while (Date.now() - start < timeoutMs) {
try {
const health = unwrap(await client.global.health());
if (health.healthy) {
return health;
}
lastError = "Server reported unhealthy";
} catch (error) {
lastError = error instanceof Error ? error.message : "Unknown error";
}
await new Promise((resolve) => setTimeout(resolve, pollMs));
}
throw new Error(lastError ?? "Timed out waiting for server health");
}
View on GitHub (pinned to 2b7df46e8a)
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.
Example fix
// before: fixed budget
await waitForHealthy(server, { timeoutMs: 5000 });
// after: larger budget on cold start
await waitForHealthy(server, { timeoutMs: process.env.CI ? 30000 : 10000 }); Defensive patterns
Strategy: retry
Validate before calling
// probe before relying on waitForHealthy's deadline
const reachable = await fetch(`http://${host}:${port}/health`).then(r => r.ok).catch(() => false);
if (!reachable) console.warn("Server port not reachable; startup may have failed."); Try / catch
try {
await waitForHealthy(server, { timeoutMs: 15000 });
} catch (err) {
if (err instanceof Error && /health|Unknown error/i.test(err.message)) {
// inspect spawned process logs and exit code before giving up
const logs = server.getStderr?.() ?? "";
throw new Error(`Server failed to become healthy: ${err.message}\nstderr: ${logs.slice(-500)}`);
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Missing/incompatible opencode server binary, port already in use or firewalled, machine under load slowing startup, first install downloading dependencies while health polls expire.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Connection lost
- Request timed out.
- OpenWork server did not become ready after activation.
- source_fetch_failed
- MCP_SHUTDOWN_FAILED
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/8edd4411b9d1e229.
Report an issue: GitHub.