block/buzz · critical · anyhow::Error

Failed to bind UDS

Error message

Failed to bind UDS {uds_path}: {e}

What it means

After the stale-socket cleanup in serve(), tokio::net::UnixListener::bind(uds_path) creates the actual Unix domain socket. Failure is wrapped with the path and aborts boot. Common causes are filesystem-level: missing or non-writable parent directory, path components denying search/execute permission, a path longer than the kernel's sun_path limit (~104-108 bytes), or a read-only filesystem.

Solutions

  1. Pre-create the parent directory with writable permissions: `install -d -o <relay-user> -m 0755 /run/buzz` and use BUZZ_UDS_PATH=/run/buzz/relay.sock.
  2. Shorten BUZZ_UDS_PATH to well under 108 bytes total.
  3. If SELinux/AppArmor is involved, allow socket creation in that directory context (or move the socket to a permitted location like /run).
  4. In containers, mount a tmpfs at the socket directory.
  5. Run the relay as a user that owns the socket directory.

Example fix

# before
export BUZZ_UDS_PATH=/var/run/buzz/relay/nested/deep/socket.sock
# error: Failed to bind UDS ...: Permission denied (or path too long)

# after
sudo install -d -o $USER -m 0755 /run/buzz
export BUZZ_UDS_PATH=/run/buzz/relay.sock
Defensive patterns

Strategy: validation

Validate before calling

p="$BUZZ_UDS_PATH"
dir=$(dirname "$p")
[ -d "$dir" ] && [ -w "$dir" ] || { echo "socket dir $dir missing or not writable" >&2; exit 1; }
[ "${#p}" -lt 100 ] || { echo "UDS path too long (kernel sun_path limit ~108)" >&2; exit 1; }

Prevention

When it happens

Trigger: UnixListener::bind at crates/buzz-relay/src/main.rs:1349 fails with EACCES (parent dir owned by root, relay running as non-root), ENOENT (parent directory does not exist), ENAMETOOLONG/EINVAL (BUZZ_UDS_PATH longer than the ~108-byte sun_path limit, typical with deeply nested /var/run/very/long/service/name.sock paths), or EROFS (path under a read-only mount).

Common situations: Running the relay as an unprivileged user with BUZZ_UDS_PATH=/var/run/buzz.sock (root-owned /var/run); long namespaced paths under /run/user/1000/... in deeply nested worktrees; SELinux denying socket creation in the target directory; container with a read-only rootfs and no tmpfs at the socket path.

Related errors


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

Appendix: source

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

        .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();
        });

        let mut tcp_rx = shutdown_tx.subscribe();
        axum::serve(
            tcp_listener,
            router.into_make_service_with_connect_info::<std::net::SocketAddr>(),
        )

View on GitHub (pinned to ef2aa1ae38)