facebook/relay · error · std::io::Error

Another daemon is already running

Error message

Another daemon is already running

What it means

The relay daemon's start_server binds a Unix socket at the configured socket_path. Before binding it checks whether a live process is already listening on that path; if socket_has_listener returns true, another daemon instance holds the socket, so it aborts with an AddrInUse io::Error to prevent two daemons competing for compiler work.

Source

Thrown at compiler/crates/relay-compiler/src/server_daemon/socket.rs:76

    /// request supplies both `flush_manifest_path` and `flush_shard_dir`.
    /// When `None`, every `Write` flushes straight to disk.
    pub flush_writer_factory: Option<FlushWriterFactory>,
}

/// Start the Unix domain socket server.
///
/// This function binds to the specified socket path and accepts client connections
/// in a loop. Each client connection is handled in a separate task.
///
/// The server will gracefully shut down when the shutdown signal is received.
pub async fn start_server<TPerfLogger: PerfLogger + 'static>(
    config: ServerConfig<TPerfLogger>,
) -> Result<(), std::io::Error> {
    let socket_path = config.socket_path;
    if socket_path.exists() {
        if socket_has_listener(&socket_path) {
            error!("Another daemon is already running on {:?}", socket_path);
            return Err(std::io::Error::new(
                std::io::ErrorKind::AddrInUse,
                "Another daemon is already running",
            ));
        } else {
            info!(
                "Stale socket found with no connected process. Removing {:?}",
                socket_path
            );
            std::fs::remove_file(&socket_path)?;
        }
    }

    let listener = UnixListener::bind(&socket_path)?;
    info!("Server daemon listening on {:?}", socket_path);

    // Write metadata so clients can discover this daemon
    let metadata = DaemonMetadata {
        socket_path: socket_path.clone(),

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Stop the existing daemon first (e.g. `relay server stop` or kill the relay process) and retry
  2. Check `lsof <socket_path>` to find the PID holding the socket and kill it
  3. Use a different --config path (the socket path is hashed from the config path) to run an independent daemon
  4. If the daemon is actually dead but the error persists, the socket should have been cleaned as stale; remove the socket file manually and restart

Example fix

// before
relay start &
relay start            // panics/fails: AddrInUse
// after
relay server stop
relay start
Defensive patterns

Strategy: validation

Validate before calling

const socketPath = config.socketPath;
if (fs.existsSync(socketPath)) {
  try {
    const client = net.connect(socketPath);
    await new Promise((res, rej) => client.once('connect', res).once('error', rej));
    client.end();
    throw new Error('Daemon already running; stop it or reuse it.');
  } catch (e) {
    // no listener -> stale socket, safe to start
  }
}

Try / catch

try {
  await startDaemon();
} catch (e) {
  if (e.code === 'EADDRINUSE') {
    console.log('Daemon already running; reusing existing daemon.');
  } else throw e;
}

Prevention

When it happens

Trigger: Running `relay start` (or any command that spawns the daemon) while a previous relay daemon is still alive and listening on the same hashed socket path derived from the config path.

Common situations: Starting a second relay daemon in another terminal; a CI job colliding with a locally running daemon; stale daemons left over from an editor/LSP integration that never exited.


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/a80464edd97c5691. Report an issue: GitHub.