ruvnet/ruflo · error · Error
Stdio transport already running
Error message
Stdio transport already running
What it means
StdioTransport.start() wires a readline interface over process.stdin (or a configured stream) and is single-shot: a private `running` flag makes a second start() throw 'Stdio transport already running'. Only stop(), which closes the interface, resets the flag — and because process.stdin cannot be reopened once closed, restarting the same stdio instance is usually wrong anyway.
Source
Thrown at v3/@claude-flow/mcp/src/transport/stdio.ts:55
private errors = 0;
private readonly inputStream: NodeJS.ReadableStream;
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;
}
async start(): Promise<void> {
if (this.running) {
throw new Error('Stdio transport already running');
}
this.logger.info('Starting stdio transport');
this.rl = readline.createInterface({
input: this.inputStream,
crlfDelay: Infinity,
});
this.rl.on('line', (line) => {
this.handleLine(line);
});
this.rl.on('close', () => {
this.handleClose();
});
this.inputStream.on('error', (error) => {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Ensure exactly one start() per instance; remove the duplicate init path
- For restarts, construct a new StdioTransport (stdin cannot be re-read after close) instead of stop/start on the same instance
- Route lifecycle through TransportManager.startAll()/stopAll()
Example fix
// before const transport = createStdioTransport(logger); await transport.start(); await transport.start(); // throws // after const transport = createStdioTransport(logger); await transport.start(); // restart path: build a fresh instance // const fresh = createStdioTransport(logger); await fresh.start();
Defensive patterns
Strategy: validation
Validate before calling
// StdioTransport.running is private; guard at the call site
let stdioStarted = false;
async function ensureStdioStarted(t: ITransport) {
if (stdioStarted) return;
await t.start();
stdioStarted = true;
} Try / catch
try {
await stdioTransport.start();
} catch (e) {
if (e instanceof Error && e.message === 'Stdio transport already running') {
// already up - no-op
} else {
throw e;
}
} Prevention
- Start the stdio transport exactly once from a single init path
- For restarts build a new StdioTransport - process.stdin cannot be reopened after stop()
- Let TransportManager own the lifecycle instead of manual start calls
When it happens
Trigger: Starting the MCP stdio server twice (e.g. start called in main and again in a spawned worker); an init block re-running under nodemon/vitest watch while a module-level singleton survives; a retry wrapper around start().
Common situations: CLI that starts the server in both foreground and spawn mode; double 'ready' handlers firing start(); shared module state surviving hot reloads.
Related errors
- Stdio transport already running
- HTTP transport already running
- TransportManager already running
- WebSocket transport already running
- SSRF guard: invalid URL — ${rawUrl}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/6ea2cf38d99d6770.
Report an issue: GitHub.