{"record":{"id":"9bbce1e1fd3a7e45","repo":"commaai/openpilot","slug":"error-prefix-failed-reason-subject-sd-ge","errorCode":null,"errorMessage":"{error_prefix} failed: {reason}/{subject} - {sd.get('message', 'unknown')}","messagePattern":"(.+?) failed: (.+?)/(.+?) - (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"openpilot/common/esim/lpa.py","lineNumber":431,"sourceCode":"\ndef es9p_request(smdp_address: str, endpoint: str, payload: dict, error_prefix: str = \"Request\", session: requests.Session | None = None) -> dict:\n  url = f\"https://{smdp_address}/gsma/rsp2/es9plus/{endpoint}\"\n  headers = {\"User-Agent\": \"gsma-rsp-lpad\", \"X-Admin-Protocol\": \"gsma/rsp/v2.3.0\", \"Content-Type\": \"application/json\"}\n  http = session or requests\n  resp = http.post(url, json=payload, headers=headers, timeout=HTTP_TIMEOUT, verify=GSMA_CI_BUNDLE)\n  resp.raise_for_status()\n  if not resp.content:\n    return {}\n  data = resp.json()\n  if \"header\" in data and \"functionExecutionStatus\" in data[\"header\"]:\n    status = data[\"header\"][\"functionExecutionStatus\"]\n    if status.get(\"status\") == \"Failed\":\n      sd = status.get(\"statusCodeData\", {})\n      reason = sd.get(\"reasonCode\", \"unknown\")\n      subject = sd.get(\"subjectCode\", \"unknown\")\n      msg = ES9P_ERROR_MESSAGES.get((reason, subject),\n            f\"{error_prefix} failed: {reason}/{subject} - {sd.get('message', 'unknown')}\")\n      raise RuntimeError(msg)\n  return data\n\n\n# --- Notifications ---\n\ndef list_notifications(client: AtClient) -> list[dict]:\n  response = es10x_command(client, encode_tlv(TAG_LIST_NOTIFICATION, b\"\"))\n  root = require_tag(response, TAG_LIST_NOTIFICATION, \"ListNotificationResponse\")\n  metadata_list = find_tag(root, TAG_OK)\n  if metadata_list is None:\n    return []\n  return [decode_struct(value, NOTIFICATION) for tag, value in iter_tlv(metadata_list) if tag == TAG_NOTIFICATION_METADATA]\n\n\ndef process_notifications(client: AtClient) -> None:\n  for notification in list_notifications(client):\n    seq_number, smdp_address = notification[\"seqNumber\"], notification[\"notificationAddress\"]\n    try:","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/common/esim/lpa.py#L413-L449","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before\nresp = es9p_request(smdp, 'gsma/rsp2/es9plus/initiateAuthentication', payload)\n# RuntimeError: Request failed: 3.8/9.2.5 - unknown\n\n# after — surface codes for diagnosis, special-case unrecoverable ones\nfrom openpilot.common.esim.lpa import es9p_request\n\ntry:\n  resp = es9p_request(smdp, 'initiateAuthentication', payload, error_prefix='InitiateAuth')\nexcept RuntimeError as e:\n  if '3.8/' in str(e):\n    raise SystemExit('profile no longer downloadable; get a new QR code') from e\n  raise","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from openpilot.common.esim.lpa import es9p_request\n\ntry:\n  data = es9p_request(smdp, endpoint, payload, error_prefix='InitiateAuth')\nexcept RuntimeError as e:\n  msg = str(e)\n  if '3.8/' in msg or 'already' in msg:\n    raise SystemExit('QR/profile consumed or expired; new QR required') from e\n  if '1.2/' in msg:\n    # server temporarily unavailable -> backoff and retry later\n    ...\n  raise","preventionTips":["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"],"tags":["esim","es9p","smdp-plus","network","carrier","python"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}