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
- Stop the existing daemon first (e.g. `relay server stop` or kill the relay process) and retry
- Check `lsof <socket_path>` to find the PID holding the socket and kill it
- Use a different --config path (the socket path is hashed from the config path) to run an independent daemon
- 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
- Always stop the daemon before starting a new one
- Use a distinct config path per workspace so socket paths don't collide
- Check with lsof/ss whether the socket has a live listener before start
- In CI, run daemonless builds to avoid socket contention
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.