{"record":{"id":"6ea2cf38d99d6770","repo":"ruvnet/ruflo","slug":"stdio-transport-already-running","errorCode":null,"errorMessage":"Stdio transport already running","messagePattern":"Stdio transport already running","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/mcp/src/transport/stdio.ts","lineNumber":55,"sourceCode":"  private errors = 0;\n\n  private readonly inputStream: NodeJS.ReadableStream;\n  private readonly outputStream: NodeJS.WritableStream;\n  private readonly maxMessageSize: number;\n\n  constructor(\n    private readonly logger: ILogger,\n    config: StdioTransportConfig = {}\n  ) {\n    super();\n    this.inputStream = config.inputStream || process.stdin;\n    this.outputStream = config.outputStream || process.stdout;\n    this.maxMessageSize = config.maxMessageSize || 10 * 1024 * 1024;\n  }\n\n  async start(): Promise<void> {\n    if (this.running) {\n      throw new Error('Stdio transport already running');\n    }\n\n    this.logger.info('Starting stdio transport');\n\n    this.rl = readline.createInterface({\n      input: this.inputStream,\n      crlfDelay: Infinity,\n    });\n\n    this.rl.on('line', (line) => {\n      this.handleLine(line);\n    });\n\n    this.rl.on('close', () => {\n      this.handleClose();\n    });\n\n    this.inputStream.on('error', (error) => {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/mcp/src/transport/stdio.ts#L37-L73","documentation":"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.","triggerScenarios":"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().","commonSituations":"CLI that starts the server in both foreground and spawn mode; double 'ready' handlers firing start(); shared module state surviving hot reloads.","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()"],"exampleFix":"// before\nconst transport = createStdioTransport(logger);\nawait transport.start();\nawait transport.start(); // throws\n\n// after\nconst transport = createStdioTransport(logger);\nawait transport.start();\n// restart path: build a fresh instance\n// const fresh = createStdioTransport(logger); await fresh.start();","handlingStrategy":"validation","validationCode":"// StdioTransport.running is private; guard at the call site\nlet stdioStarted = false;\nasync function ensureStdioStarted(t: ITransport) {\n  if (stdioStarted) return;\n  await t.start();\n  stdioStarted = true;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await stdioTransport.start();\n} catch (e) {\n  if (e instanceof Error && e.message === 'Stdio transport already running') {\n    // already up - no-op\n  } else {\n    throw e;\n  }\n}","preventionTips":["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"],"tags":["transport","stdio","lifecycle","mcp"],"backgroundTag":"already-running","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}