commaai/openpilot · warning · RuntimeError

sector erase verification failed

Error message

sector erase verification failed

What it means

RuntimeError inside program_sector(): after erase_sector(addr), the tool reads the sector back and expects all 0xFF (the erased state of SPI NOR). Anything else means the erase did not actually take - block protection still set, worn-out flash cells, or unstable reads. Raised inside with_retries so the sector is retried after reconnect.

Source

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

      check_budget()
      print(f"{label} attempt {attempt}: {e}", flush=True)
      reconnect(flash)


def stable_read(flash, addr, length, count=2):
  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:

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Retry via with_retries (automatic); a transient cause recovers on the second attempt
  2. Re-run the full init() (clear block protection) if failures cluster - check that status() & 0x1C is 0 before erasing
  3. Read the sector twice (stable_read) to rule out read instability before blaming the erase
  4. If the same sector repeatedly fails to erase, the flash chip is worn - replace the enclosure; do not proceed with partial erases
Defensive patterns

Strategy: retry

Validate before calling

def sector_erase_verified(flash, addr) -> bool:
    try:
        return flash.read(addr, 4096) == b'\xff' * 4096
    except Exception:
        return False

Try / catch

# program_sector already retries internally via with_retries
# surface only persistent failures
try:
    program_sector(flash, addr, target)
except TimeoutError:
    raise  # budget exhausted: stop, do not keep looping

Prevention

When it happens

Trigger: program_sector() executes transaction(0x20, addr) then immediately flash.read(addr, SECTOR); if any of the 4096 bytes is not 0xFF this raises. Common when write_enable did not latch (erase silently ignored due to protection), the flash is worn out, or the read path is corrupt (compounding error 72).

Common situations: Block-protection bits not cleared (init() path failed or was skipped); a sector that exceeded its erase-cycle endurance and will not erase to 0xFF; marginal USB causing wrong read-back; wrong flash geometry assumptions.

Related errors


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