commaai/openpilot · error · LPAError
profile not found
Error message
profile {iccid} not found What it means
After sending the SetNickname request, the eUICC's response status tag is parsed: code 0x01 per PROFILE_ERROR_CODES means iccidOrAidNotFound. The library raises LPAError (specifically the base LPA error, cf. LPAProfileNotFoundError in the base module) so callers can distinguish 'unknown profile' from transport failures.
Solutions
- Call list_profiles(client) and use an ICCID from the fresh list
- Catch LPAError/LPAProfileNotFoundError specifically to show 'profile not found' rather than a generic failure
- Validate the ICCID format (19-20 digits, Luhn) before sending
Example fix
# before
set_profile_nickname(client, stale_iccid, 'car') # LPAError: profile ... not found
# after
from openpilot.common.esim.lpa import list_profiles, set_profile_nickname
live = {p['iccid'] for p in list_profiles(client)}
if stale_iccid not in live:
raise SystemExit(f'{stale_iccid} no longer on eUICC; profiles: {sorted(live)}')
set_profile_nickname(client, stale_iccid, 'car') Defensive patterns
Strategy: validation
Validate before calling
from openpilot.common.esim.lpa import list_profiles
live_iccids = {p['iccid'] for p in list_profiles(client)}
if iccid not in live_iccids:
raise SystemExit(f'{iccid} not on eUICC; available: {sorted(live_iccids)}') Type guard
def iccid_exists(client, iccid: str) -> bool: return any(p['iccid'] == iccid for p in list_profiles(client))
Try / catch
from openpilot.common.esim.base import LPAError
try:
set_profile_nickname(client, iccid, name)
except LPAError as e:
if 'not found' in str(e):
# refresh and retry once with a live iccid
...
raise Prevention
- Never cache ICCIDs across user interactions; re-list before operating
- Catch LPAError separately from transport errors to give accurate user feedback
- Validate ICCID length/Luhn before sending to the eUICC
When it happens
Trigger: set_profile_nickname() with an ICCID that does not exist on the eUICC: stale ICCID captured before a delete, typo'd digit, or an ICCID for a different device's eUICC.
Common situations: UI holds a cached profile list that is out of date; profile was deleted by another flow; ICCID transcribed with a missing/extra digit (the TBCD encoding also changes if an odd digit count is mishandled).
Related errors
- APDU failed with SW=
- AuthenticateServer rejected by eUICC
- Invalid BoundProfilePackage
- Missing '}
- Profile nickname must be 64 bytes or less
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/9f3623efe6300a95.
Report an issue: GitHub.
Appendix: source
Thrown at openpilot/common/esim/lpa.py:407
list_ok = find_tag(root, TAG_OK)
if list_ok is None:
return []
return [decode_struct(value, PROFILE) for tag, value in iter_tlv(list_ok) if tag == 0xE3]
def list_profiles(client: AtClient) -> list[dict]:
return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00"))
def set_profile_nickname(client: AtClient, iccid: str, nickname: str) -> None:
nickname_bytes = nickname.encode("utf-8")
if len(nickname_bytes) > 64:
raise ValueError("Profile nickname must be 64 bytes or less")
content = encode_tlv(TAG_ICCID, string_to_tbcd(iccid)) + encode_tlv(0x90, nickname_bytes)
response = es10x_command(client, encode_tlv(TAG_SET_NICKNAME, content))
code = require_tag(require_tag(response, TAG_SET_NICKNAME, "SetNicknameResponse"), TAG_STATUS, "SetNickname status")[0]
if code == 0x01:
raise LPAError(f"profile {iccid} not found")
if code != 0x00:
raise RuntimeError(f"SetNickname failed with status 0x{code:02X}")
# --- ES9P HTTP ---
def es9p_request(smdp_address: str, endpoint: str, payload: dict, error_prefix: str = "Request", session: requests.Session | None = None) -> dict:
url = f"https://{smdp_address}/gsma/rsp2/es9plus/{endpoint}"
headers = {"User-Agent": "gsma-rsp-lpad", "X-Admin-Protocol": "gsma/rsp/v2.3.0", "Content-Type": "application/json"}
http = session or requests
resp = http.post(url, json=payload, headers=headers, timeout=HTTP_TIMEOUT, verify=GSMA_CI_BUNDLE)
resp.raise_for_status()
if not resp.content:
return {}
data = resp.json()
if "header" in data and "functionExecutionStatus" in data["header"]:
status = data["header"]["functionExecutionStatus"]
if status.get("status") == "Failed":View on GitHub (pinned to 516ec1e682)