ruvnet/ruflo · error · Error
MCP Server already running (PID: ${status.pid})
Error message
MCP Server already running (PID: ${status.pid}) What it means
Thrown by MCPServerManager.start() when getStatus() reports a running server whose PID differs from the current process. The guard exists so a second `mcp start` does not try to bind a port / spawn a duplicate stdio child while a prior server holds the PID file. The current-process PID is excluded because in stdio mode getStatus() can transiently report running=true for the very process about to start.
Source
Thrown at v3/@claude-flow/cli/src/mcp-server.ts:179
// spread last below and therefore takes precedence over this env fallback.
const environmentTools = parseMcpToolSelection(process.env.CLAUDE_FLOW_MCP_TOOLS);
this.options = {
...DEFAULT_OPTIONS,
...(environmentTools === 'all' ? {} : { tools: environmentTools }),
...options,
};
}
/**
* Start the MCP server
*/
async start(): Promise<MCPServerStatus> {
// Check if already running (skip if status reports our own PID —
// getStatus() returns running=true for the current process in stdio mode
// even before the server is actually started)
const status = await this.getStatus();
if (status.running && status.pid !== process.pid) {
throw new Error(`MCP Server already running (PID: ${status.pid})`);
}
const startTime = performance.now();
this.startTime = new Date();
this.emit('starting', { options: this.options });
try {
if (this.options.transport === 'stdio') {
// For stdio transport, spawn the server process
await this.startStdioServer();
} else {
// For HTTP/WebSocket, start in-process server
await this.startHttpServer();
}
const duration = performance.now() - startTime;
View on GitHub (pinned to 6b01dc5a68)
Solutions
- Check the reported PID is actually an MCP server: `ps -p <pid> -o command=` and `npx @claude-flow/cli mcp status`.
- If it is a stale/zombie entry, stop it cleanly: `npx @claude-flow/cli mcp stop`, or `kill <pid>` then remove the PID file.
- If you need a second instance, set a different CLAUDE_FLOW_MCP_PORT / PID file path for the new one.
- If start() was called twice in-process, call stop() between them, or guard with getStatus() first.
Example fix
// before — second start races the first await manager.start(); await manager.start(); // throws // after — stop or reuse await manager.start(); // ... work ... await manager.stop(); await manager.start();
Defensive patterns
Strategy: validation
Validate before calling
const status = await manager.getStatus();
if (status.running && status.pid !== process.pid) {
// decide: stop the existing, or use a different port
throw new Error(`refusing to start: another MCP server holds the lock (PID ${status.pid})`);
} Type guard
null
Try / catch
try { await manager.start(); }
catch (e) {
if (/MCP Server already running \(PID:/.test(String(e?.message ?? ''))) {
await manager.stop(true); // or pick a new port
await manager.start();
} else throw e;
} Prevention
- Always pair start() with stop() in a finally block.
- Use distinct PID-file paths / ports per process when running multiple instances.
- Clean up the PID file in a process-exit handler so a crash does not leave a stale lock.
When it happens
Trigger: A previous `mcp start` (HTTP or stdio) crashed without removing its PID file, leaving a stale lock; a daemon is genuinely running on the configured port; calling start() twice in the same script without stop(); a prior server forked a child that survived its parent.
Common situations: Daemon was killed -9 and left the PID file; port 3000 (or CLAUDE_FLOW_MCP_PORT) is held by an older instance; a CI runner reused a workspace without cleanup; two concurrent MCP startup paths (hook + manual start) race.
Related errors
- Server failed to start within timeout
- Issue ${input.issueId} is already claimed by ${issue.claimed
- MCP server "${server.name}" is in cooldown (HTTP ${cd.status
- MCP server "${server.name}" returned HTTP ${httpStatus}: ${h
- HTTP transport failed: ${String(firstError instanceof Error
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/c5f421aceac090c4.
Report an issue: GitHub.