commaai/openpilot · error · RuntimeError
{error_prefix} failed: {reason}/{subject} - {sd.get('message
Error message
{error_prefix} failed: {reason}/{subject} - {sd.get('message', 'unknown')} What it means
es9p_request() posts JSON to the SM-DP+ server's ES9+ endpoint and inspects header.functionExecutionStatus. When the server reports status 'Failed', it builds a message from reasonCode/subjectCode; known (reason, subject) pairs get friendly text from ES9P_ERROR_MESSAGES, unknown combinations fall back to this generic '{prefix} failed: {reason}/{subject} - {message}' RuntimeError.
Source
Thrown at openpilot/common/esim/lpa.py:431
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":
sd = status.get("statusCodeData", {})
reason = sd.get("reasonCode", "unknown")
subject = sd.get("subjectCode", "unknown")
msg = ES9P_ERROR_MESSAGES.get((reason, subject),
f"{error_prefix} failed: {reason}/{subject} - {sd.get('message', 'unknown')}")
raise RuntimeError(msg)
return data
# --- Notifications ---
def list_notifications(client: AtClient) -> list[dict]:
response = es10x_command(client, encode_tlv(TAG_LIST_NOTIFICATION, b""))
root = require_tag(response, TAG_LIST_NOTIFICATION, "ListNotificationResponse")
metadata_list = find_tag(root, TAG_OK)
if metadata_list is None:
return []
return [decode_struct(value, NOTIFICATION) for tag, value in iter_tlv(metadata_list) if tag == TAG_NOTIFICATION_METADATA]
def process_notifications(client: AtClient) -> None:
for notification in list_notifications(client):
seq_number, smdp_address = notification["seqNumber"], notification["notificationAddress"]
try:View on GitHub (pinned to 516ec1e682)
Solutions
- Look up the (reasonCode, subjectCode) pair in SGP.22 §9.5.2 tables — reason 1.x = validation, 2.x = device incompatibility, 3.8.x = the profile is gone/expired/already used
- For 3.8/8.2.6-style 'already used' errors, request a new QR code / profile from the carrier — the profile cannot be re-downloaded
- For 1.x codes, verify the request payload (transactionId, authentication challenge values) is from the same session — restart the download flow from the QR code
- Retry later for 1.2/8.1 server-unavailable; otherwise escalate to the carrier with the exact reason/subject codes
Example fix
# before
resp = es9p_request(smdp, 'gsma/rsp2/es9plus/initiateAuthentication', payload)
# RuntimeError: Request failed: 3.8/9.2.5 - unknown
# after — surface codes for diagnosis, special-case unrecoverable ones
from openpilot.common.esim.lpa import es9p_request
try:
resp = es9p_request(smdp, 'initiateAuthentication', payload, error_prefix='InitiateAuth')
except RuntimeError as e:
if '3.8/' in str(e):
raise SystemExit('profile no longer downloadable; get a new QR code') from e
raise Defensive patterns
Strategy: try-catch
Try / catch
from openpilot.common.esim.lpa import es9p_request
try:
data = es9p_request(smdp, endpoint, payload, error_prefix='InitiateAuth')
except RuntimeError as e:
msg = str(e)
if '3.8/' in msg or 'already' in msg:
raise SystemExit('QR/profile consumed or expired; new QR required') from e
if '1.2/' in msg:
# server temporarily unavailable -> backoff and retry later
...
raise Prevention
- Treat 3.8-class reasons as terminal: get a new QR code instead of retrying
- Backoff-retry 1.2/8.1 server-unavailable errors rather than hammering
- Log full reason/subject/message triple — unmapped pairs are your best bug report to carriers
When it happens
Trigger: Any ES9+ call (initiateAuthentication, authenticateClient, handleNotification) whose server-side functionExecutionStatus is Failed with reason/subject codes not in the curated map — e.g. novel carrier error codes, malformed requests rejected by policy, or server-side validation failures (8.1 subjects).
Common situations: Carrier SM-DP+ servers returning less common reason codes (e.g. 1.2/1.3 validation errors, 3.x functional errors outside the seven mapped pairs); QR code provisioned for a different device; mismatched transaction state after retries.
Related errors
- Invalid smdpSigned2
- no profile at index {ref} (have {len(profiles)})
- no eUICC detected
- AT command timed out
- AT command failed: {line}
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/9bbce1e1fd3a7e45.
Report an issue: GitHub.