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

  1. 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
  2. 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
  3. 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
  4. 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

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


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