ruvnet/RuView · error

cannot bind {addr}: {e}

Error message

cannot bind {addr}: {e}

What it means

enroll failed to UdpSocket::bind its CSI receive address {bind}:{udp_port}. Same failure family as the calibrate-serve ingest bind: AddrInUse when another CSI tool (calibrate-serve, room-watch, a second enroll) holds the UDP port, permission denied on ports below 1024, or an invalid --bind address.

Source

Thrown at v2/crates/wifi-densepose-cli/src/room.rs:149

    let (anchor, reason) = recorder.finalize(gate, now_unix());
    let feature = if anchor.quality.accepted {
        Some(AnchorFeature::from_series(room_id, label, &series, fs_hz))
    } else {
        None
    };
    Ok((feature, anchor, reason))
}

/// Execute `enroll`.
pub async fn enroll(args: EnrollArgs) -> Result<()> {
    let baseline = load_baseline(&args.baseline)?;
    let baseline_id = baseline.calibration_uuid().to_string();
    let gate = AnchorQualityGate::default();

    let addr = format!("{}:{}", args.bind, args.udp_port);
    let socket = UdpSocket::bind(&addr)
        .await
        .map_err(|e| anyhow::anyhow!("cannot bind {addr}: {e}"))?;
    eprintln!("[enroll] room='{}' baseline={} on udp://{addr}", args.room_id, &baseline_id[..8]);
    eprintln!("[enroll] follow each prompt; bad captures are re-prompted.");

    let mut session = EnrollmentSession::new(&args.room_id, &baseline_id, now_unix());
    let mut features: Vec<AnchorFeature> = Vec::new();

    for label in AnchorLabel::SEQUENCE {
        let mut accepted = false;
        for attempt in 1..=args.attempts {
            let (feat, anchor, reason) =
                capture_anchor(&socket, &baseline, &gate, label, &args.tier, args.fs_hz, &args.room_id)
                    .await?;
            if anchor.quality.accepted {
                eprintln!(
                    "[enroll]   ✓ accepted (presence_z={:.2} motion={:.0}% frames={})",
                    anchor.quality.presence_z,
                    anchor.quality.motion_rate * 100.0,
                    anchor.quality.frames

View on GitHub (pinned to 4685618388)

Solutions

  1. Stop the other CSI-consuming process or give enroll its own --udp-port
  2. Match the firmware's CSI destination port to the one enroll binds
  3. Use a high port; avoid ports below 1024 unless privileged
  4. Re-run enroll once the port is free — the baseline file is not affected

Example fix

// before
$ ruview calibrate-serve &   # still bound to udp/9999
$ ruview enroll --udp-port 9999   # cannot bind

// after
$ ruview calibrate-serve &
$ ruview enroll --udp-port 9998   # dedicated port, firmware retargeted
Defensive patterns

Strategy: validation

Validate before calling

fn udp_port_free(bind: &str, port: u16) -> bool {
    std::net::UdpSocket::bind((bind, port)).is_ok()
}

Try / catch

match UdpSocket::bind(&addr).await {
    Ok(socket) => socket,
    Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
        return Err(anyhow::anyhow!("udp://{addr} already in use — stop calibrate-serve/room-watch or change --udp-port"));
    }
    Err(e) => return Err(anyhow::anyhow!("cannot bind {addr}: {e}")),
}

Prevention

When it happens

Trigger: Running enroll while calibrate-serve or room-watch is bound to the same port (all CSI ingest tools compete for the UDP port); a privileged port as non-root; a malformed --bind value.

Common situations: Following the calibrate to enroll workflow without stopping the calibration server; parallel sessions on one host; the port changed on the firmware side only.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/944a44780a2b7d0a. Report an issue: GitHub.