block/buzz · error · anyhow::Error

BUZZ_UDS_PATH exists but is not a socket

Error message

BUZZ_UDS_PATH {uds_path} exists but is not a socket

What it means

When BUZZ_UDS_PATH is set on Unix, serve() inspects the path with symlink_metadata before binding a Unix domain socket. If an entry exists, is a socket file left by a previous run, it is silently removed; anything else (regular file, directory, symlink — note symlink_metadata does not follow symlinks, so a symlink's own type is 'symlink') is a hard error. The check protects against both destroying user data at that path and binding a socket behind an attacker-controlled indirection.

Solutions

  1. Inspect the existing entry: `ls -l <path>` / `file <path>`.
  2. If it is a stale regular file or symlink you control, remove it: `rm <path>` (or `rmdir` for a directory) and restart the relay.
  3. If the path belongs to another service, choose a different BUZZ_UDS_PATH (e.g. /run/buzz-relay.sock).
  4. If you intended socket-activation-style symlink indirection, drop it — bind the real socket path directly; the relay will not follow symlinks for UDS.
  5. Fix provisioning scripts to `rm -f` the socket path instead of `touch`ing it.

Example fix

# before
export BUZZ_UDS_PATH=/var/run/buzz.sock
# ls -l /var/run/buzz.sock  -> regular file touched by systemd unit
# error: BUZZ_UDS_PATH /var/run/buzz.sock exists but is not a socket

# after
rm /var/run/buzz.sock
# relay restart removes stale *socket* entries automatically from now on
Defensive patterns

Strategy: validation

Validate before calling

# before starting with BUZZ_UDS_PATH set, verify the path is absent or a socket
p="$BUZZ_UDS_PATH"
if [ -e "$p" ] && [ "$(stat -c %F "$p")" != "socket" ]; then
  echo "$p exists and is not a socket" >&2; exit 1
fi

Type guard

fn is_socket_or_absent(p: &std::path::Path) -> bool {
    use std::os::unix::fs::FileTypeExt;
    match std::fs::symlink_metadata(p) {
        Ok(m) => m.file_type().is_socket(),
        Err(_) => true,
    }
}

Prevention

When it happens

Trigger: Setting BUZZ_UDS_PATH to: (1) a path occupied by a regular file (e.g. a config or log file); (2) an existing directory; (3) a symlink pointing at the real socket location — symlink_metadata reports the link itself, which is not a socket, so this errors instead of silently unlinking through the link; (4) a leftover FIFO or device node. Only a concrete socket file at that exact path is auto-cleaned.

Common situations: BUZZ_UDS_PATH=/var/run/buzz.sock where /var/run/buzz.sock was pre-created with `touch` by a provisioning script; pointing the env var at a symlink placed by an init system that manages socket activation; reusing a path that another daemon already uses for its own non-socket state.

Related errors


AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20). Data as JSON: /api/errors/f8fd02dc615031dc. Report an issue: GitHub.

Appendix: source

Thrown at crates/buzz-relay/src/main.rs:1459

            "Signalled restart close to all live WebSocket connections"
        );
        hard_shutdown_abort
    });

    let tcp_listener = tokio::net::TcpListener::bind(&config.bind_addr)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to bind {}: {e}", config.bind_addr))?;
    info!(addr = %config.bind_addr, "buzz-relay TCP listening");

    #[cfg(unix)]
    if let Some(ref uds_path) = config.uds_path {
        use std::os::unix::fs::FileTypeExt as _;
        match std::fs::symlink_metadata(uds_path) {
            Ok(meta) if meta.file_type().is_socket() => {
                let _ = std::fs::remove_file(uds_path);
            }
            Ok(_) => {
                return Err(anyhow::anyhow!(
                    "BUZZ_UDS_PATH {uds_path} exists but is not a socket"
                ));
            }
            Err(_) => {}
        }
        let uds_listener = tokio::net::UnixListener::bind(uds_path)
            .map_err(|e| anyhow::anyhow!("Failed to bind UDS {uds_path}: {e}"))?;
        info!(path = %uds_path, "buzz-relay UDS listening");

        let router_uds = router.clone();
        let mut uds_rx = shutdown_tx.subscribe();
        let uds_handle = tokio::spawn(async move {
            axum::serve(uds_listener, router_uds.into_make_service())
                .with_graceful_shutdown(async move {
                    uds_rx.changed().await.ok();
                })
                .await
                .ok();

View on GitHub (pinned to ef2aa1ae38)