commaai/openpilot · error · RuntimeError

invalid config backup: {path}

Error message

invalid config backup: {path}

What it means

RuntimeError from saved_config(): a config backup file already exists at CONFIG_DIR/<hostname>.bin (the O_EXCL open failed with FileExistsError), but its size is not exactly 0x100 (256) bytes - the size of one config page. The tool keeps a host-local backup of the device's config sector so a reflash can restore it; a malformed backup cannot be trusted and is rejected instead of silently used.

Source

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

        if flash.read(addr + off, len(chunk)) != chunk:
          raise RuntimeError(f"page verify failed at 0x{addr + off:05x}")
    if flash.read(addr, SECTOR) != target:
      raise RuntimeError("sector verification failed")
  with_retries(flash, f"sector 0x{addr:05x}", program)


def config_path():
  return os.path.join(CONFIG_DIR, f"{os.uname().nodename}.bin")


def saved_config(path, data):
  os.makedirs(os.path.dirname(path), exist_ok=True)
  try:
    fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
  except FileExistsError as e:
    backup = open(path, "rb").read()
    if len(backup) != 0x100:
      raise RuntimeError(f"invalid config backup: {path}") from e
    if backup != data:
      print(f"restoring config from {path}", flush=True)
    return backup
  with os.fdopen(fd, "wb") as f:
    f.write(data)
    f.flush()
    os.fsync(f.fileno())
  return data


def rom_write(image, config):
  # the ROM bootloader implements only the BOT protocol, and requires a port reset before bulk transfers
  path, _, _ = find_chestnut()
  if path is None:
    raise RuntimeError("chestnut disappeared before recovery")
  unbind_drivers(path)
  fd = open_device(path)
  try:

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Inspect the file with ls -l /data/chestnut_config/<hostname>.bin - anything not 256 bytes is not a valid config page
  2. If you accept losing the old backup, move it aside (mv file file.bak) and re-run; the tool will write a fresh backup from the device's current config
  3. Verify the current on-device config reads back stably (stable_read at 0x100) before letting it become the new backup
  4. Preserve valid 256-byte backups carefully - they are the only recovery source for device-specific config after a bad flash

Example fix

# recover from malformed backup
mv /data/chestnut_config/$(hostname).bin /data/chestnut_config/$(hostname).bin.corrupt
# re-run flasher to create a fresh backup
Defensive patterns

Strategy: validation

Validate before calling

import os

def backup_valid(path) -> bool:
    return not os.path.exists(path) or (os.path.getsize(path) == 0x100)

Try / catch

try:
    cfg = saved_config(path, data)
except RuntimeError as e:
    if 'invalid config backup' in str(e):
        archive_and_regenerate(path)  # mv aside, re-run for fresh backup

Prevention

When it happens

Trigger: saved_config(path, data) when /data/chestnut_config/<nodename>.bin exists and len(backup) != 256: a previous run was interrupted mid-write, the file was manually edited or truncated, or a different format version wrote it.

Common situations: Interrupted earlier flash leaving a short file; disk-full during the original backup; someone copied a config from another tool; a hostname reused across hardware generations with different config sizes.


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