commaai/openpilot · warning · RuntimeError

page verify failed at 0x{addr + off:05x}

Error message

page verify failed at 0x{addr + off:05x}

What it means

RuntimeError inside program_sector(): after flash.program(addr+off, chunk) wrote one 128-byte page, the immediate read-back did not match the data written. The page program either did not commit (write protection, WEL dropped), committed partially, or the read-back is corrupt. Raised inside with_retries, so the whole sector is erased and retried cleanly.

Source

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

  def read():
    reads = [flash.read(addr, length) for _ in range(count)]
    if any(x != reads[0] for x in reads[1:]):
      raise RuntimeError(f"unstable flash read at 0x{addr:05x}")
    return reads[0]
  return with_retries(flash, f"read 0x{addr:05x}", read)


def program_sector(flash, addr, target):
  def program():
    flash.erase_sector(addr)
    if flash.read(addr, SECTOR) != bytes([0xFF]) * SECTOR:
      raise RuntimeError("sector erase verification failed")
    for off in range(0, SECTOR, PAGE):
      chunk = target[off:off + PAGE]
      if chunk != bytes([0xFF]) * len(chunk):
        flash.program(addr + off, chunk)
        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:

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Rely on with_retries: the sector is re-erased and rewritten from the source data, which fixes transient bit errors
  2. If pages fail consistently at the same address, map it: a repeat offender is a worn page - replace the hardware
  3. Verify link stability first with stable_read on an untouched region; if reads alone are unstable, fix the cable/port before blaming the flash
  4. Confirm init() ran on this connection so write-enable actually latches
Defensive patterns

Strategy: retry

Try / catch

try:
    program_sector(flash, addr, target)
except TimeoutError:
    raise  # FLASH_BUDGET exhausted - persistent page failures elsewhere

Prevention

When it happens

Trigger: program_sector()'s per-page loop: flash.program() does write_buffer + write_enable + transaction(0x02, addr, len, mode=1) + wait_write_done, then flash.read(addr+off, len(chunk)) != chunk triggers this. One flipped bit in read-back is enough.

Common situations: Marginal USB link corrupting either the write payload or the verify read; block protection re-asserting; a weak flash cell in that page; controller register state lost between write and verify due to an implicit device reset.

Related errors


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