commaai/openpilot · error · LPAError

refusing to delete a comma profile

Error message

refusing to delete a comma profile

What it means

delete_profile refuses with LPAError when the target profile has is_comma set — a safety interlock preventing deletion of the device's own comma-managed operational profile. Deleting it could break the device's cellular connectivity managed by comma.

Source

Thrown at openpilot/common/esim/lpa.py:742

        )
        for p in list_profiles(self._client)
      ]

  def get_active_profile(self) -> Profile | None:
    return None

  def process_notifications(self) -> None:
    if not system_time_valid():
      raise RuntimeError("System time is not set; TLS certificate validation requires a valid clock")
    with self._acquire_channel():
      process_notifications(self._client)

  def delete_profile(self, iccid: str) -> None:
    profile = next((p for p in self.list_profiles() if p.iccid == iccid), None)
    if profile is None:
      raise LPAProfileNotFoundError(f"profile not found: {iccid}")
    if profile.is_comma:
      raise LPAError("refusing to delete a comma profile")
    with self._acquire_channel():
      request = encode_tlv(TAG_DELETE_PROFILE, encode_tlv(TAG_ICCID, string_to_tbcd(iccid)))
      response = es10x_command(self._client, request)
      code = require_tag(require_tag(response, TAG_DELETE_PROFILE, "DeleteProfileResponse"), TAG_STATUS, "DeleteProfile status")[0]
    if code != PROFILE_OK:
      raise LPAError(f"DeleteProfile failed: {PROFILE_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})")

  def download_profile(self, qr: str, nickname: str | None = None) -> None:
    with self._acquire_channel():
      iccid = download_profile(self._client, qr)
      if nickname and iccid:
        set_profile_nickname(self._client, iccid, nickname)

  def nickname_profile(self, iccid: str, nickname: str) -> None:
    with self._acquire_channel():
      set_profile_nickname(self._client, iccid, nickname)

  def _enable_profile(self, iccid: str) -> int:

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Skip is_comma profiles when iterating list_profiles for deletion
  2. Delete only user-installed profiles identified by ICCID
  3. If intentional decommissioning is needed, use comma's official deprovisioning path rather than delete_profile

Example fix

// before
for p in lpa.list_profiles():
    lpa.delete_profile(p.iccid)

// after
for p in lpa.list_profiles():
    if not p.is_comma:
        lpa.delete_profile(p.iccid)
Defensive patterns

Strategy: type-guard

Validate before calling

target = next((p for p in lpa.list_profiles() if p.iccid == iccid), None)
if target is None or target.is_comma:
    skip_delete(iccid)

Type guard

def is_deletable(profile) -> bool:
    return not profile.is_comma

Try / catch

try:
    lpa.delete_profile(iccid)
except LPAError as e:
    if "comma profile" in str(e):
        log.warning("refusing to delete comma profile")
    else:
        raise

Prevention

When it happens

Trigger: Calling delete_profile on the profile whose metadata marks it as the comma provisioning profile (profile.is_comma True).

Common situations: User or tool attempts to wipe all profiles including the device's own; automated cleanup loops that iterate every profile from list_profiles().

Related errors


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