ruvnet/ruflo · error · Error

Stdio transport already running

Error message

Stdio transport already running

What it means

StdioTransport.start() attaches a readline interface to the configured input stream (process.stdin by default, overridable via config.inputStream) and installs line handlers; it is guarded by a running flag. A second start() throws because the reader and handlers are already installed on the stream - restarting readline on a live stream would duplicate message processing.

Source

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

  private readonly outputStream: NodeJS.WritableStream;
  private readonly maxMessageSize: number;

  constructor(
    private readonly logger: ILogger,
    config: StdioTransportConfig = {}
  ) {
    super();
    this.inputStream = config.inputStream || process.stdin;
    this.outputStream = config.outputStream || process.stdout;
    this.maxMessageSize = config.maxMessageSize || 10 * 1024 * 1024; // 10MB default
  }

  /**
   * Start the transport
   */
  async start(): Promise<void> {
    if (this.running) {
      throw new Error('Stdio transport already running');
    }

    this.logger.info('Starting stdio transport');

    // Create readline interface for efficient line processing
    this.rl = readline.createInterface({
      input: this.inputStream,
      crlfDelay: Infinity,
    });

    // Handle incoming lines
    this.rl.on('line', (line) => {
      this.handleLine(line);
    });

    // Handle close
    this.rl.on('close', () => {
      this.handleClose();

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Memoize startup: started ??= transport.start()
  2. Restart via await transport.stop() (removes the readline interface) then start()
  3. Or construct a fresh StdioTransport with explicit inputStream/outputStream for the new lifecycle

Example fix

// before
await stdio.start();
onReconnect(async () => { await stdio.start(); }); // throws

// after
let started = false;
async function ensureStarted() {
  if (!started) { await stdio.start(); started = true; }
}
await ensureStarted();
Defensive patterns

Strategy: validation

Validate before calling

let started = false;
async function ensureStdioStarted() {
  if (started) return;
  await stdioTransport.start();
  started = true;
}

Try / catch

try {
  await stdioTransport.start();
} catch (e) {
  if (e instanceof Error && /already running/.test(e.message)) return;
  throw e;
}

Prevention

When it happens

Trigger: Calling start() twice on the same stdio transport; restart-on-reconnect code that forgets stop(); wiring the same transport instance into two bootstrap blocks; a supervisor loop that calls start() each time stdin data arrives.

Common situations: CLI tools assembling stdio transport in multiple init blocks; hot reload re-running bootstrap; tests sharing a module-scoped transport.

Related errors


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