ruvnet / ruvnet/RuView

fix(provision): per-device state keyed by serial port path, so a different board inherits the previous board's node_id

Open
#1,755 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug firmware
Dominant language
Rust
Stars
94.5k
Forks
12.5k
Avg merge
21h 27m
Merged PRs (30d)
43

Description

Summary

provision.py keys its per-device state file by serial port path only, with no board
identity, so provisioning a different board on a port previously used by another board
silently offers the earlier board's settings — including node_id, which is the fusion cohort
key.

provision.py:126-129:

def _state_path_for(port: str, state_dir: str) -> str:
    safe = port.replace("/", "_").replace(":", "_").replace("\\", "_")
    return os.path.join(state_dir, f"{safe}.json")

Port names are reused aggressively — on macOS a second board plugged into the same hub slot
commonly enumerates as the same /dev/cu.usbmodemXXXX.

To be fair to the current design: the merge behaviour is documented (provision.py:22),
--reset is offered for recycled boards (:31, :362), and the merged values are printed
before flashing (:443). This is a footgun rather than a hidden failure. But the printed
values look identical whether they were chosen by the operator or inherited from a different
physical board
, so there is nothing to notice.

Why now

Hit three times while provisioning three ESP32-S3 boards in one session. Each new board was
offered the previous board's node_id. Without --reset every board would have been
provisioned as the same node id, which on a multistatic deployment silently corrupts the
cohort rather than failing loudly.

Reproduction

  1. provision.py --port <PORT> --node-id 2 ... with board A.
  2. Unplug A, plug board B into the same port (same device path).
  3. python3 provision.py --port <PORT> --state → prints board A's node_id, target_port
    and channel.
  4. Provisioning B without --reset writes those values to B.

Done when

State is bound to board identity, so a different board on the same port does not inherit.

Work items

  • Key the state file by chip identity — esptool read-mac / chip id — instead of, or in
    addition to, the port path. The script already talks to the chip.
  • When a port's stored identity does not match the connected board, do not merge silently:
    either ignore the stale state or require --reset.
  • Mark inherited values distinctly in the pre-flash summary (:443), so "inherited from a
    previous board" is visually different from "you passed this".

Note

--force-partial is marked deprecated "since #391/#574 … the script now merges with prior
state by default", which is the change that introduced this. Worth checking those for prior
discussion.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with provision.py:22, 126-129, and the pre-flash summary around line 443; run the reproduction with two boards sharing one port path. Check the existing chip communication and esptool read-mac support, then review issues #391 and #574. Done means a changed chip identity cannot silently inherit state, and inherited values are visibly distinct or require --reset.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, embedded-iot
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.