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
- Retry via with_retries (automatic); a transient cause recovers on the second attempt
- Re-run the full init() (clear block protection) if failures cluster - check that status() & 0x1C is 0 before erasing
- Read the sector twice (stable_read) to rule out read instability before blaming the erase
- 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
- Check status() & 0x1C == 0 before starting sector programming
- Use with_retries-wrapped program_sector rather than manual erase/program
- Retire enclosures whose sectors repeatedly fail to erase
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
- page verify failed at 0x{addr + off:05x}
- sector verification failed
- SPI flash WIP timeout
- unstable flash read at 0x{addr:05x}
- {func.__name__} failed after retry
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/53bc9a9a24863c5c.
Report an issue: GitHub.