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
- Inspect the file with ls -l /data/chestnut_config/<hostname>.bin - anything not 256 bytes is not a valid config page
- 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
- Verify the current on-device config reads back stably (stable_read at 0x100) before letting it become the new backup
- 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
- Never hand-edit config backup files
- Keep /data from filling up - partial writes follow full disks
- Snapshot the config backup directory before flashing campaigns
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.