commaai/openpilot · error · RuntimeError
send_apdu failed
Error message
send_apdu failed
What it means
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.
Source
Thrown at openpilot/common/esim/lpa.py:257
def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]:
for attempt in range(SEND_APDU_RETRIES):
try:
if not self.channel:
self.open_isdr()
hex_payload = apdu.hex().upper()
for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'):
if line.startswith("+CGLA:"):
parts = line.split(":", 1)[1].split(",", 1)
if len(parts) == 2:
data = bytes.fromhex(parts[1].strip().strip('"'))
if len(data) >= 2:
return data[:-2], data[-2], data[-1]
raise RuntimeError("Missing +CGLA response")
except (RuntimeError, ValueError):
self.channel = None
if attempt == SEND_APDU_RETRIES - 1:
raise
raise RuntimeError("send_apdu failed")
# --- TLV utilities ---
def iter_tlv(data: bytes, with_positions: bool = False) -> Generator:
idx, length = 0, len(data)
while idx < length:
start_pos = idx
tag = data[idx]
idx += 1
if tag & 0x1F == 0x1F: # Multi-byte tag
tag_value = tag
while idx < length:
next_byte = data[idx]
idx += 1
tag_value = (tag_value << 8) | next_byte
if not (next_byte & 0x80):
breakView on GitHub (pinned to 516ec1e682)
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
Defensive patterns
Strategy: try-catch
Try / catch
try:
data, sw1, sw2 = client.send_apdu(apdu)
except RuntimeError as e:
# the real cause is the original exception; 'send_apdu failed' indicates a modified/broken loop
log.error('send_apdu exhausted retries: %s', e)
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Failed to open ISD-R after retries
- Missing +CGLA response
- no profile at index {ref} (have {len(profiles)})
- no eUICC detected
- AT command timed out
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/976fc672630bf109.
Report an issue: GitHub.