ruvnet/ruflo · error · Error

Server failed to start within timeout

Error message

Server failed to start within timeout

What it means

Thrown by MCPServerManager.waitForReady() after polling checkHealth() every 100ms for the configured timeout (default 10000ms) without ever seeing healthy=true. This is the HTTP/WebSocket path only — stdio returns immediately. It means the in-process HTTP server started but never answered the health endpoint (or checkHealth() keeps failing its TCP/HTTP probe).

Source

Thrown at v3/@claude-flow/cli/src/mcp-server.ts:751

   * Wait for server to be ready
   */
  private async waitForReady(timeout = 10000): Promise<void> {
    // For stdio transport, we're ready immediately (in-process)
    if (this.options.transport === 'stdio') {
      return;
    }

    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      const health = await this.checkHealth();
      if (health.healthy) {
        return;
      }
      await this.sleep(100);
    }

    throw new Error('Server failed to start within timeout');
  }

  /**
   * Wait for process to exit
   */
  private async waitForExit(timeout: number): Promise<void> {
    if (!this.process) return;

    return new Promise((resolve) => {
      const timer = setTimeout(() => {
        resolve();
      }, timeout);

      this.process!.once('exit', () => {
        clearTimeout(timer);
        resolve();
      });
    });

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Raise the timeout: pass a larger `timeout` to waitForReady, or set the relevant startup-timeout option in MCPServerOptions.
  2. Manually probe the health endpoint (`curl http://localhost:<port>/health`) to see if the server is up but the route differs from what checkHealth expects.
  3. Check that the port is actually free and bindable: `lsof -i:<port>` / `ss -ltn`.
  4. Profile cold-start — if sql.js/embeddings init dominates, warm those before start() or move to a persisted cache.

Example fix

// before — default 10s timeout too short in slow CI
await manager.start();
// after
await manager.start({ startupTimeoutMs: 30000 });
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

const tryStart = async (timeoutMs: number) => {
  try { return await manager.start({ startupTimeoutMs: timeoutMs }); }
  catch (e) {
    if (/Server failed to start within timeout/.test(String(e?.message ?? ''))) {
      // backoff and retry once with a longer timeout
      await new Promise(r => setTimeout(r, 2000));
      return manager.start({ startupTimeoutMs: timeoutMs * 2 });
    }
    throw e;
  }
};

Prevention

When it happens

Trigger: The HTTP server bind() is blocked by a firewall or the port is in TIME_WAIT; the underlying createMCPServer threw asynchronously after start() returned; the health endpoint route is misconfigured; the timeout is too short for a slow-booting dependency (sql.js WASM load, embeddings model init).

Common situations: CI environment with slow disk or network causing cold-start > 10s; port conflict where the OS hands out the port slowly; a regression in the health route; the host is resource-starved (CPU throttled container).

Understand the failure class

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/cf441ad7ecf382ca. Report an issue: GitHub.