commaai/openpilot · error · RuntimeError

bundled firmware is {expected_product!r}, expected version {

Error message

bundled firmware is {expected_product!r}, expected version {expected_version}

What it means

Raised by flash_chestnut() when the caller passes an expected_version argument but the product string embedded in the bundled firmware image does not equal 'custom {expected_version}-CLEAN'. It is a pre-flight sanity check that runs before any hardware is touched, comparing image_product(image) against the version you claimed. The firmware bundle on disk and the version argument you passed disagree, so the flash aborts to avoid writing an unexpected build.

Source

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

        print(f"chestnut re-enumerated with {product!r}, firmware activates on its next power cycle", flush=True)
      return
    time.sleep(0.2)
  print("chestnut did not re-enumerate, firmware activates on its next power cycle", flush=True)


def defer_signal(signum, _frame):
  # writing from a handler must not reenter a print already in progress
  os.write(1, f"signal {signum} deferred until the chestnut is powered back up\n".encode())


def flash_chestnut(expected_version=None, force=False):
  global _deadline

  image = FIRMWARE_PATH.read_bytes()
  validate_image(image)
  expected_product = image_product(image)
  if expected_version is not None and expected_product != f"custom {expected_version}-CLEAN":
    raise RuntimeError(f"bundled firmware is {expected_product!r}, expected version {expected_version}")

  path, vid_pid, product = find_chestnut()
  if path is None:
    print("no chestnut connected", flush=True)
    return
  if product == expected_product and not force:
    print(f"chestnut firmware is up to date ({expected_product})", flush=True)
    return

  _deadline = time.monotonic() + FLASH_BUDGET
  for pm_path in PM_PATHS:
    disable_runtime_pm(pm_path)

  previous = {sig: signal.signal(sig, defer_signal) for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP)}
  try:
    if in_rom_bootloader(vid_pid, product):
      if not recover_from_rom(image, expected_product):
        return

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Re-run without the version argument to flash whatever is bundled: sudo python flash.py
  2. If you know the bundled version, pass exactly that hash: sudo python flash.py <correct-version>
  3. If the bundle is stale, restore the correct firmware file at FIRMWARE_PATH (git checkout / re-download) and retry with the expected version
  4. Verify what the bundle actually contains by calling image_product(FIRMWARE_PATH.read_bytes()) and compare to 'custom {version}-CLEAN'

Example fix

# before
flash_chestnut(expected_version="abc123")  # bundle has a different build

# after
prod = image_product(FIRMWARE_PATH.read_bytes())
print(prod)  # e.g. 'custom def456-CLEAN'
flash_chestnut(expected_version="def456")
Defensive patterns

Strategy: validation

Validate before calling

from openpilot.system.hardware.chestnut.flash import FIRMWARE_PATH, image_product, validate_image
image = FIRMWARE_PATH.read_bytes()
product = image_product(image)  # e.g. 'custom abc123-CLEAN'
expected = f"custom {args.version}-CLEAN"
assert args.version is None or product == expected, f"bundle={product!r} requested={expected!r}"

Try / catch

try:
    flash_chestnut(expected_version=ver, force=force)
except RuntimeError as e:
    if 'bundled firmware is' in str(e):
        print(f"version mismatch: {e}; re-run with the printed product's hash")
        raise SystemExit(2)
    raise

Prevention

When it happens

Trigger: Running 'python flash.py <version>' where <version> does not match the product string baked into FIRMWARE_PATH; rebuilding/replacing the bundled firmware image without updating the CLI argument; passing a full product string instead of just the version hash.

Common situations: Version skew after a partial checkout or stale firmware bundle; CI passing the release version while the working tree carries a locally built firmware; typo in the version hash on the command line.

Related errors


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