commaai/openpilot · error · RuntimeError

expected one chestnut, found

Error message

expected one chestnut, found {len(found)}

What it means

RuntimeError from find_chestnut() in the chestnut flasher when more than one USB device matches the known VID:PID sets (normal or ROM bootloader IDs). The flash logic assumes exactly one candidate device; with several, it cannot know which to flash, so it aborts before touching anything.

Solutions

  1. Unplug all but the single chestnut device you intend to flash and rerun
  2. Power off/remove the extra unit — including one stuck in ROM bootloader mode from a prior failed flash
  3. List matches first via lsusb (compare VID:PID) to identify the duplicate

Example fix

// before
# two chestnut boards plugged in
flash_chestnut()  # RuntimeError: expected one chestnut, found 2

// after
# unplug extra board, keep only target
flash_chestnut()
Defensive patterns

Strategy: validation

Validate before calling

from openpilot.system.hardware.chestnut.flash import find_chestnut, VID_PIDS, ROM_VID_PIDS

def exactly_one_chestnut() -> bool:
    path, vid_pid, product = find_chestnut()
    return path is not None and not multiple_candidates()

Prevention

When it happens

Trigger: Having two chestnut boards (or a second device that shares the AS2462-based VID:PID, including one already sitting in ROM bootloader mode alongside a normally enumerated one) plugged in when the flasher scans /sys/bus/usb/devices.

Common situations: A bench with multiple units under test, a device half-flashed from a previous run enumerating in ROM mode while a second unit is attached, or unrelated hardware reusing the same chip.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/fa1ca01f1e02f02f. Report an issue: GitHub.

Appendix: source

Thrown at openpilot/system/hardware/chestnut/flash.py:71

  _fields_ = [("ep", ctypes.c_uint), ("len", ctypes.c_uint),
              ("timeout", ctypes.c_uint), ("data", ctypes.c_void_p)]


class RomFallback(Exception):
  pass


def find_chestnut():
  found = []
  for d in glob.glob("/sys/bus/usb/devices/*"):
    try:
      vid_pid = (open(d + "/idVendor").read().strip(), open(d + "/idProduct").read().strip())
      if vid_pid in VID_PIDS + ROM_VID_PIDS:
        found.append((d, vid_pid, open(d + "/product").read().strip()))
    except OSError:
      pass
  if len(found) > 1:
    raise RuntimeError(f"expected one chestnut, found {len(found)}")
  return found[0] if found else (None, None, None)


def in_rom_bootloader(vid_pid, product):
  # the ROM bootloader reports the config page strings, or its own when the config page is lost
  return vid_pid in ROM_VID_PIDS or product == ROM_PRODUCT or (product or "").startswith("AS2462")


def disable_runtime_pm(path):
  control = os.path.join(path, "power/control")
  if not os.path.exists(control):
    return
  with open(control, "w") as f:
    f.write("on\n")
  if open(control).read().strip() != "on":
    raise RuntimeError(f"could not disable USB runtime PM: {control}")
  delay = os.path.join(path, "power/autosuspend_delay_ms")
  if os.path.exists(delay):

View on GitHub (pinned to 516ec1e682)