commaai/openpilot · error · RuntimeError

APDU failed with SW={sw1:02X}{sw2:02X}

Error message

APDU failed with SW={sw1:02X}{sw2:02X}

What it means

es10x_command() sends APDUs to the eUICC and checks the ISO 7816 status word. SW=61xx means more data (handled via GET RESPONSE), 90xx means success; anything else raises RuntimeError with the two-byte SW in hex. The SW encodes the exact eUICC-side failure per SGP.22 (e.g. 6A88 = referenced data not found, 6985 = conditions of use not satisfied).

Source

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

def es10x_command(client: AtClient, data: bytes) -> bytes:
  response = bytearray()
  sequence = 0
  offset = 0
  while offset < len(data):
    chunk = data[offset : offset + ES10X_MSS]
    offset += len(chunk)
    is_last = offset == len(data)
    apdu = bytes([0x80, 0xE2, 0x91 if is_last else 0x11, sequence & 0xFF, len(chunk)]) + chunk
    segment, sw1, sw2 = client.send_apdu(apdu)
    response.extend(segment)
    while True:
      if sw1 == 0x61:  # More data available
        segment, sw1, sw2 = client.send_apdu(bytes([0x80, 0xC0, 0x00, 0x00, sw2 or 0]))
        response.extend(segment)
        continue
      if (sw1 & 0xF0) == 0x90:
        break
      raise RuntimeError(f"APDU failed with SW={sw1:02X}{sw2:02X}")
    sequence += 1
  return bytes(response)


# --- Profile operations ---

NOTIFICATION: FieldMap = {
  TAG_STATUS: ("seqNumber", lambda v: int.from_bytes(v, "big")),
  0x81: ("profileManagementOperation",
         lambda v: NOTIFICATION_OPERATIONS.get(next((m for m in NOTIFICATION_OPERATIONS if len(v) >= 2 and v[1] & m), 0), "unknown")),
  0x0C: ("notificationAddress", lambda v: v.decode("utf-8", errors="ignore")),
  TAG_ICCID: ("iccid", tbcd_to_string),
}


def decode_profiles(blob: bytes) -> list[dict]:
  root = require_tag(blob, TAG_PROFILE_INFO_LIST, "ProfileInfoList")
  list_ok = find_tag(root, TAG_OK)

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Decode SW1SW2: 6A88 referenced data not found, 6A80 incorrect parameters, 6985 use conditions not satisfied (e.g. profile already in target state), 6E00 unsupported instruction
  2. Re-list profiles (list_profiles) to refresh actual state, then retry against current ICCIDs/states
  3. If deleting/switching an enabled profile, disable it first when the policy requires disabled-state operations (profileNotInDisabledState behavior)
  4. On repeated unexpected SWs, reopen the ISD-R channel (channel loss can corrupt APDU framing)

Example fix

# before
result = es10x_command(client, encode_tlv(TAG_ENABLE_PROFILE, content))
# RuntimeError: APDU failed with SW=6A88

# after — refresh state and target an existing, correctly-stated profile
profiles = list_profiles(client)
assert any(p['iccid'] == iccid for p in profiles), 'iccid not on eUICC'
# ensure target is disabled before enable per policy
result = es10x_command(client, encode_tlv(TAG_ENABLE_PROFILE, content))
Defensive patterns

Strategy: try-catch

Validate before calling

from openpilot.common.esim.lpa import list_profiles

profiles = list_profiles(client)
target = next((p for p in profiles if p['iccid'] == iccid), None)
if target is None:
  raise SystemExit('iccid not present; refresh list')
# for enable: ensure profile currently disabled
if action == 'enable' and target['profileState'] == 1:
  raise SystemExit('profile already enabled')

Try / catch

try:
  result = es10x_command(client, cmd_bytes)
except RuntimeError as e:
  msg = str(e)
  if 'SW=6A88' in msg:
    # referenced data not found -> refresh profile state
    ...
  elif 'SW=6985' in msg:
    # conditions not satisfied (e.g. wrong profile state) -> adjust and retry
    ...
  raise

Prevention

When it happens

Trigger: The eUICC refuses an operation: enabling a profile that doesn't exist (6A88), enabling an already-enabled profile (6985 / wrongProfileReenabling), deleting an enabled profile, or protocol errors during a store/load sequence. Any ES10x command whose SW1 high nibble isn't 9 or 6-with-61.

Common situations: Race where profile state changed between list and switch/delete (profile already enabled/deleted); stale channel after modem hiccup causing garbage APDUs; eUICC policy (carrier) disallowing the operation.

Related errors


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