ruvnet/RuView · error · anyhow::Error
bad node id in {spec:?}
Error message
bad node id in {spec:?} What it means
After splitting the `--node-bank` value on the first colon, the id part is parsed as `u8`. Anything that is not an integer in 0..=255 — out-of-range values like `300`, letters, or a Windows drive letter (`C:\banks\n1.json` makes `C` the id) — fails the parse and raises this error before the bank is read.
Source
Thrown at v2/crates/wifi-densepose-cli/src/room.rs:417
last_print = Instant::now();
}
}
Ok(())
}
/// Multistatic `room-watch`: fuse several co-located nodes (ADR-029/151).
async fn room_watch_multi(args: RoomWatchArgs) -> Result<()> {
use std::collections::{BTreeMap, VecDeque};
let mut mix = MultiNodeMixture::new();
let mut node_ids: Vec<u8> = Vec::new();
for spec in &args.node_bank {
let (id_s, path) = spec
.split_once(':')
.ok_or_else(|| anyhow::anyhow!("--node-bank must be N:path (got {spec:?})"))?;
let id: u8 = id_s
.parse()
.map_err(|_| anyhow::anyhow!("bad node id in {spec:?}"))?;
let raw = std::fs::read_to_string(path)
.map_err(|e| anyhow::anyhow!("cannot read {path}: {e}"))?;
let bank = SpecialistBank::from_json(&raw).map_err(|e| anyhow::anyhow!("{e}"))?;
let baseline = bank.baseline_id.clone();
mix.add_node(id, bank, baseline);
node_ids.push(id);
}
eprintln!("[room-watch] multistatic over nodes {node_ids:?}");
let addr = format!("{}:{}", args.bind, args.udp_port);
let socket = UdpSocket::bind(&addr)
.await
.map_err(|e| anyhow::anyhow!("cannot bind {addr}: {e}"))?;
eprintln!("[room-watch] fusing on udp://{addr} (window={} frames)", args.window);
let mut buf = vec![0u8; RECV_BUF];
let mut wins: BTreeMap<u8, VecDeque<f32>> = BTreeMap::new();
let start = Instant::now();View on GitHub (pinned to 4685618388)
Solutions
- Use a node id in 0..=255 that matches the firmware-configured id: `--node-bank 3:./banks/node3.json`.
- On Windows, pass a relative path so the first colon is the `N:` separator, not the drive letter.
- Trim whitespace and drop leading `+`/zeros beyond one digit from ids in scripts.
Example fix
# before (Windows drive colon swallowed as separator) room-watch --node-bank C:\banks\n1.json # after room-watch --node-bank 1:banks\n1.json
Defensive patterns
Strategy: validation
Validate before calling
fn node_id_in_range(spec: &str) -> bool {
spec.split_once(':')
.map(|(id, _)| id.parse::<u8>().is_ok())
.unwrap_or(false)
} Type guard
fn parse_node_bank_spec(spec: &str) -> Option<(u8, &str)> {
let (id, path) = spec.split_once(':')?;
let id = id.parse::<u8>().ok()?;
(!path.is_empty()).then(|| (id, path))
} Prevention
- Remember split_once(':') splits at the FIRST colon — never put a drive-letter absolute path after the id on Windows.
- Keep the node-id map (0..=255) documented next to the physical ESP32 nodes.
- Strip whitespace/signs from ids sourced from spreadsheets or env vars.
When it happens
Trigger: `--node-bank 256:bank.json`; `--node-bank node1:bank.json`; on Windows an absolute path `--node-bank C:\banks\n1.json` where `split_once(':')` consumes the drive-letter colon; whitespace or a sign in the id.
Common situations: Node ids planned above 255 in firmware; Windows paths; copy-paste of ids like `01 ` with trailing space from a spreadsheet.
Related errors
- Rectangle requires 4 values: min_x,min_y,max_x,max_y (got {}
- Circle requires 3 values: center_x,center_y,radius (got {})
- --node-bank must be N:path (got {spec:?})
- cannot read {path}: {e}
- Invalid axis token {token!r}; expected one of {sorted(_AXIS_
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/2e5caae6f7119c12.
Report an issue: GitHub.