ruvnet/RuView · error · anyhow::Error
cannot read {path}: {e}
Error message
cannot read {path}: {e} What it means
In the multistatic loop, each bank path extracted from `N:path` is read with `std::fs::read_to_string`, and any OS error is surfaced verbatim: ENOENT (missing file), EACCES (no read permission), EISDIR (a directory was passed), or 'stream did not contain valid UTF-8' for binary content. The file must be the plain-text JSON `SpecialistBank` serialization produced by the training/enroll tooling.
Source
Thrown at v2/crates/wifi-densepose-cli/src/room.rs:419
}
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();
let mut last_print = Instant::now();
View on GitHub (pinned to 4685618388)
Solutions
- Pass an absolute path: `--node-bank 1:/home/op/banks/node1.json`.
- Verify the file is readable plain JSON: `test -r <path> && head -c1 <path>` should print `{`.
- Fix permissions (`chmod +r`) or ownership if the error text says `Permission denied`.
Example fix
# before (launched by systemd, CWD=/) room-watch --node-bank 1:node1.json # after room-watch --node-bank 1:/opt/ruview/banks/node1.json
Defensive patterns
Strategy: validation
Validate before calling
fn bank_loadable(path: &str) -> bool {
let p = std::path::Path::new(path);
p.is_file()
&& std::fs::metadata(p).map(|m| !m.permissions().readonly()).unwrap_or(false)
&& std::fs::read_to_string(p).map(|s| s.trim_start().starts_with('{')).unwrap_or(false)
} Try / catch
Err(e) => {
if let Some(io) = e.downcast_ref::<std::io::Error>() {
match io.kind() {
std::io::ErrorKind::NotFound => eprintln!("bank file missing: check path/CWD"),
std::io::ErrorKind::PermissionDenied => eprintln!("bank unreadable: chmod +r"),
_ => eprintln!("bank read failed: {io}"),
}
}
} Prevention
- Use absolute bank paths when launching from service managers whose CWD differs.
- Regenerate banks with the same CLI version that consumes them.
- Validate the first byte is '{' in deploy scripts to catch binary/truncated transfers.
When it happens
Trigger: Running the CLI from a different working directory than the bank's relative path; passing a directory as the path; a gzipped or otherwise binary bank file; unreadable ownership after an scp as root.
Common situations: Relative paths breaking when launched by systemd or a script whose CWD is `/`; files transferred with wrong ownership; trailing newline/whitespace injected into the path by shell variables.
Related errors
- cannot write {output}: {e}
- cannot read baseline {path}: {e} — run `calibrate` first
- cannot write {}: {e}
- cannot read {}: {e} — run `enroll` first
- cannot read geometry {path}: {e}
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/4519a46f413e3066.
Report an issue: GitHub.