ruvnet/ruflo · error · Error

HTTP transport already running

Error message

HTTP transport already running

What it means

HttpTransport.start() is not idempotent: a private `running` flag flips to true when the express server begins listening, and any second start() on the same instance throws 'HTTP transport already running'. The flag is private and only stop() resets it, so this error always means the same transport object was started twice without an intervening stop().

Source

Thrown at v3/@claude-flow/mcp/src/transport/http.ts:75

  private messagesReceived = 0;
  private messagesSent = 0;
  private errors = 0;
  private httpRequests = 0;
  private wsMessages = 0;

  constructor(
    private readonly logger: ILogger,
    private readonly config: HttpTransportConfig
  ) {
    super();
    this.app = express();
    this.setupMiddleware();
    this.setupRoutes();
  }

  async start(): Promise<void> {
    if (this.running) {
      throw new Error('HTTP transport already running');
    }

    this.logger.info('Starting HTTP transport', {
      host: this.config.host,
      port: this.config.port,
    });

    this.server = createServer(this.app);

    this.wss = new WebSocketServer({
      server: this.server,
      path: '/ws',
    });

    this.setupWebSocketHandlers();

    await new Promise<void>((resolve, reject) => {
      this.server!.listen(this.config.port, this.config.host, () => {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Call await transport.stop() before start() when restarting the same instance
  2. Create a fresh instance via createHttpTransport(logger, cfg) for each lifecycle instead of restarting the old one
  3. Route lifecycle through TransportManager and use its startAll()/stopAll()/isRunning()
  4. Audit for duplicate start() call paths (bootstrap + reconnect/retry) and remove one

Example fix

// before
await httpTransport.start(); // throws if already started

// after
async function startOnce(t: ITransport) {
  try {
    await t.start();
  } catch (e) {
    if (!(e instanceof Error && e.message.includes('already running'))) throw e;
    await t.stop();
    await t.start();
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// HttpTransport.running is private; track lifecycle at the call site
let httpStarted = false;
async function ensureHttpStarted(t: ITransport) {
  if (httpStarted) return;
  await t.start();
  httpStarted = true;
}

Try / catch

try {
  await httpTransport.start();
} catch (e) {
  if (e instanceof Error && e.message === 'HTTP transport already running') {
    // already up - treat as no-op
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling server.start() (or httpTransport.start()) from two code paths, e.g. bootstrap plus a health-check/reconnect handler; hot-reload (nodemon, vitest watch) re-running initialization while the old module-level instance survives; a retry loop around start() firing again after a slow success.

Common situations: Module-singleton transports re-initialized on re-import; MCP server started in both a setup hook and the test body; 'ensure running' helpers that call start() unconditionally.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/efdc87e9d8fffe89. Report an issue: GitHub.