{"record":{"id":"976fc672630bf109","repo":"commaai/openpilot","slug":"send-apdu-failed","errorCode":null,"errorMessage":"send_apdu failed","messagePattern":"send_apdu failed","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"openpilot/common/esim/lpa.py","lineNumber":257,"sourceCode":"  def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]:\n    for attempt in range(SEND_APDU_RETRIES):\n      try:\n        if not self.channel:\n          self.open_isdr()\n        hex_payload = apdu.hex().upper()\n        for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},\"{hex_payload}\"'):\n          if line.startswith(\"+CGLA:\"):\n            parts = line.split(\":\", 1)[1].split(\",\", 1)\n            if len(parts) == 2:\n              data = bytes.fromhex(parts[1].strip().strip('\"'))\n              if len(data) >= 2:\n                return data[:-2], data[-2], data[-1]\n        raise RuntimeError(\"Missing +CGLA response\")\n      except (RuntimeError, ValueError):\n        self.channel = None\n        if attempt == SEND_APDU_RETRIES - 1:\n          raise\n    raise RuntimeError(\"send_apdu failed\")\n\n\n# --- TLV utilities ---\n\ndef iter_tlv(data: bytes, with_positions: bool = False) -> Generator:\n  idx, length = 0, len(data)\n  while idx < length:\n    start_pos = idx\n    tag = data[idx]\n    idx += 1\n    if tag & 0x1F == 0x1F:  # Multi-byte tag\n      tag_value = tag\n      while idx < length:\n        next_byte = data[idx]\n        idx += 1\n        tag_value = (tag_value << 8) | next_byte\n        if not (next_byte & 0x80):\n          break","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/common/esim/lpa.py#L239-L275","documentation":"send_apdu() retries each APDU up to SEND_APDU_RETRIES=3 times on RuntimeError/ValueError (which includes missing +CGLA responses and bad hex parsing). This trailing raise is a defensive unreachable-in-practice branch: the last retry re-raises the original error, so 'send_apdu failed' would only appear if the loop exited without an exception on the final attempt.","triggerScenarios":"Structurally unreachable in normal execution — the except block re-raises on the last attempt. Encountering this message would indicate control-flow tampering or an unexpected exception type escaping the loop; what users actually see after retry exhaustion is the underlying 'Missing +CGLA response', 'AT command failed', or 'AT command timed out' error.","commonSituations":"Essentially never in stock code; grep for it only if a fork modified the except clause or retry constants (e.g. SEND_APDU_RETRIES set to 0, making the loop body never run).","solutions":["If you see this message, inspect the real failure one frame up / enable DEBUG=1 for the raw AT transcript","Check SEND_APDU_RETRIES was not configured to 0 or negative, which empties the retry loop and falls through to this raise","In forks, ensure the last-attempt re-raise remains so callers get the root-cause exception"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n  data, sw1, sw2 = client.send_apdu(apdu)\nexcept RuntimeError as e:\n  # the real cause is the original exception; 'send_apdu failed' indicates a modified/broken loop\n  log.error('send_apdu exhausted retries: %s', e)\n  raise","preventionTips":["Don't modify SEND_APDU_RETRIES to 0/负值 in forks; keep last-attempt re-raise intact","Instrument retries with logging when forking so root causes stay visible","Treat this exact message as a code-smell signal, not a runtime condition"],"tags":["esim","defensive-code","unreachable","retries","python"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}