commaai/openpilot · error · RuntimeError

Missing {label or f'tag 0x{target:X}'}

Error message

Missing {label or f'tag 0x{target:X}'}

What it means

require_tag() walks a BER-TLV structure looking for a specific tag and throws RuntimeError naming the missing tag (hex) or a human label like 'SetNickname status' when it is absent. It is the generic guard for malformed or unexpected eUICC/eSIM response payloads whose structure deviates from SGP.22 expectations.

Source

Thrown at openpilot/common/esim/lpa.py:302

      if idx + num_bytes > length:
        break
      size = int.from_bytes(data[idx : idx + num_bytes], "big")
      idx += num_bytes
    if idx + size > length:
      break
    value = data[idx : idx + size]
    idx += size
    yield (tag_value, value, start_pos, idx) if with_positions else (tag_value, value)


def find_tag(data: bytes, target: int) -> bytes | None:
  return next((v for t, v in iter_tlv(data) if t == target), None)


def require_tag(data: bytes, target: int, label: str = "") -> bytes:
  v = find_tag(data, target)
  if v is None:
    raise RuntimeError(f"Missing {label or f'tag 0x{target:X}'}")
  return v


def tbcd_to_string(raw: bytes) -> str:
  return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9)


def string_to_tbcd(s: str) -> bytes:
  digits = [int(c) for c in s if c.isdigit()]
  return bytes(digits[i] | ((digits[i + 1] if i + 1 < len(digits) else 0xF) << 4) for i in range(0, len(digits), 2))


def encode_tlv(tag: int, value: bytes) -> bytes:
  tag_bytes = bytes([(tag >> 8) & 0xFF, tag & 0xFF]) if tag > 255 else bytes([tag])
  vlen = len(value)
  if vlen <= 127:
    return tag_bytes + bytes([vlen]) + value
  length_bytes = vlen.to_bytes((vlen.bit_length() + 7) // 8, "big")

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Enable DEBUG=1 to dump the raw AT/+CGLA exchange and inspect the actual TLV bytes returned by the eUICC
  2. Verify you are parsing the response that matches the command you sent (correct BFxx tag pair)
  3. If the eUICC is noncompliant, use find_tag() with tolerant handling instead of require_tag() for optional elements
  4. Reset modem/eUICC and retry — a corrupted APDU exchange (segmentation/61xx handling) can produce garbled responses that parse partially

Example fix

# before
status = require_tag(response, TAG_STATUS, 'SetNickname status')[0]
# RuntimeError: Missing SetNickname status

# after — tolerate optional absence, fail with context
from openpilot.common.esim.lpa import find_tag, TAG_STATUS

status = find_tag(response, TAG_STATUS)
if status is None:
  raise RuntimeError(f'unexpected eUICC response layout: {response.hex()}')
code = status[0]
Defensive patterns

Strategy: try-catch

Try / catch

from openpilot.common.esim.lpa import require_tag, RuntimeError

try:
  value = require_tag(data, TAG_STATUS, 'status')
except RuntimeError as e:
  if str(e).startswith('Missing'):
    log.error('unexpected eUICC response: %s', data.hex())
    client.channel = None
    client.open_isdr()  # retry from a clean channel
  raise

Prevention

When it happens

Trigger: Any ES10x response parsing where the expected TLV element is missing: a SetNicknameResponse without its status tag, a ListNotificationResponse lacking the expected wrapper, or an eUICC returning truncated/noncompliant TLV. Also triggered if a response for a different command is passed in by mistake.

Common situations: eUICC firmware that is not fully SGP.22 compliant or is a newer/minor version with changed structure; response buffers truncated by ES10X_MSS=120 segmentation bugs; passing a raw response where a nested element was expected.

Related errors


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