commaai/openpilot · critical · RuntimeError

cannot recover from the ROM bootloader without a config back

Error message

cannot recover from the ROM bootloader without a config backup at {backup}

What it means

recover_from_rom() needs a previously saved 0x100-byte config blob (config_path()) to restore the chestnut's configuration while reviving it from the ROM bootloader. If that backup file does not exist, recovery cannot proceed and this RuntimeError is raised. Without the config, the device cannot be brought back onto custom firmware from ROM mode.

Source

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

  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
      # firmware is back, verify it against the bundled image
      force, product = True, None
    write_image(image, expected_product, product, force)
  finally:
    for sig, handler in previous.items():
      signal.signal(sig, handler)


def recover_from_rom(image, expected_product):
  # returns whether the chestnut came back on custom firmware
  backup = config_path()
  if not os.path.isfile(backup):
    raise RuntimeError(f"cannot recover from the ROM bootloader without a config backup at {backup}")
  config = open(backup, "rb").read()
  if len(config) != 0x100:
    raise RuntimeError(f"invalid config backup: {backup}")

  committed = False
  while True:
    check_budget()
    path, vid_pid, product = find_chestnut()
    if path is None:
      if committed:
        print("chestnut is offline, recovered firmware boots on its next power cycle", flush=True)
        return False
      vbus_cycle()
      continue
    if not in_rom_bootloader(vid_pid, product):
      return True
    if committed:
      print("chestnut stayed powered, recovered firmware boots on its next power cycle", flush=True)

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Check whether the backup exists under another account: look at config_path() for the user that originally ran flash.py and copy it to the current location
  2. Re-run flash.py from the machine/user where the chestnut was originally flashed so config_path() points at the existing backup
  3. If no backup exists anywhere, the device must be recovered through comma's official recovery/reflash path - contact support with the device state
  4. For the future: never delete the file at config_path(); back it up alongside device records
Defensive patterns

Strategy: validation

Validate before calling

import os
from openpilot.system.hardware.chestnut.flash import config_path
backup = config_path()
assert os.path.isfile(backup), f"no config backup at {backup} - do NOT enter recovery without it"
assert os.path.getsize(backup) == 0x100, f"bad backup size {os.path.getsize(backup)}"

Try / catch

try:
    recover_from_rom(image, expected_product)
except RuntimeError as e:
    if 'config backup' in str(e):
        # stop recovery attempts; escalate - device must not be power-cycled repeatedly
        raise SystemExit(f"recovery blocked: {e}")
    raise

Prevention

When it happens

Trigger: A flash was interrupted and the device fell into the ROM bootloader, but no successful earlier flash ever wrote a config backup at config_path(); the backup file was deleted or is on a different machine/user's home; first-ever flash on a fresh device failing mid-way before a backup existed.

Common situations: Running flash.py as a different user (root vs user) so config_path() resolves elsewhere; cleaning home directories or /tmp wipes the backup; recovering a device that shipped in a bad state and was never successfully flashed once.

Related errors


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