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
- Memoize startup: started ??= transport.start()
- Restart via await transport.stop() (removes the readline interface) then start()
- 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
- Track started state next to the transport instance; start exactly once
- For restarts, stop() first (it removes the readline interface) or create a new StdioTransport
- Provide explicit inputStream/outputStream in config when embedding, and own the lifecycle
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
- Stdio transport already running
- HTTP transport already running
- TransportManager already running
- WebSocket transport already running
- Server already running
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/38a496aa0bb03ffd.
Report an issue: GitHub.