commaai/openpilot · error · RuntimeError
private key is not configured
Error message
private key is not configured
What it means
Api.get_token() signs a JWT with the device's private key to authenticate to the comma API. The Api constructor loads the key pair via get_key_pair(), which scans PERSIST/comma/id_rsa(.pub) and id_ecdsa(.pub). If neither pair exists on disk, private_key stays None and get_token raises this RuntimeError instead of attempting to sign.
Source
Thrown at openpilot/common/api.py:31
class Api:
def __init__(self, dongle_id):
self.dongle_id = dongle_id
self.jwt_algorithm, self.private_key, _ = get_key_pair()
def get(self, *args, **kwargs):
return self.request('GET', *args, **kwargs)
def post(self, *args, **kwargs):
return self.request('POST', *args, **kwargs)
def request(self, method, endpoint, timeout=None, access_token=None, **params):
return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
def get_token(self, payload_extra=None, expiry_hours=1):
if self.private_key is None:
raise RuntimeError("private key is not configured")
now = datetime.now(UTC).replace(tzinfo=None)
payload = {
'identity': self.dongle_id,
'nbf': now,
'iat': now,
'exp': now + timedelta(hours=expiry_hours)
}
if payload_extra is not None:
payload.update(payload_extra)
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
if isinstance(token, bytes):
token = token.decode('utf8')
return token
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
headers = {}
if access_token is not None:View on GitHub (pinned to 516ec1e682)
Solutions
- Generate an RSA or ECDSA key pair in the persist directory: mkdir -p <persist>/comma && ssh-keygen -t ecdsa -f <persist>/comma/id_ecdsa (no passphrase), so both id_ecdsa and id_ecdsa.pub exist
- If keys exist but under a different Paths.persist_root(), verify Paths.persist_root() resolves where you expect and move/copy the comma/ directory there
- Guard calls: only invoke get_token() after checking api.get_key_pair() returns a non-None key, and skip/queue API-authenticated work otherwise
- On a real device, let the provisioning flow (e.g. setup/registration) create the keys before any Api usage
Example fix
// before
api = Api(dongle_id)
token = api.get_token() # RuntimeError: private key is not configured
# after
from openpilot.common.api import Api, get_key_pair
algorithm, private_key, _ = get_key_pair()
if private_key is None:
print('device not provisioned with keys; skipping API token')
else:
token = Api(dongle_id).get_token() Defensive patterns
Strategy: validation
Validate before calling
from openpilot.common.api import get_key_pair
algorithm, private_key, public_key = get_key_pair()
if private_key is None:
# device not provisioned; skip or trigger provisioning
raise SystemExit('no comma key pair in persist; provision the device first') Type guard
def has_private_key() -> bool: from openpilot.common.api import get_key_pair return get_key_pair()[1] is not None
Try / catch
try:
token = api.get_token()
except RuntimeError as e:
if 'private key is not configured' in str(e):
# provision keys or degrade gracefully
...
raise Prevention
- Check get_key_pair() returns a key before constructing authenticated API flows
- On new devices, run registration/provisioning before any Api usage
- Keep id_rsa(.pub) or id_ecdsa(.pub) pairs intact in PERSIST/comma — never delete just one of the pair
When it happens
Trigger: Calling Api(dongle_id).get_token(...) on a device (or dev machine) where Paths.persist_root()/comma/ does not contain both id_rsa and id_rsa.pub, or both id_ecdsa and id_ecdsa.pub. This happens on fresh installations before key generation, on non-device PCs without a persisted /comma directory, or if the persist partition was wiped.
Common situations: Running openpilot code off-device (CI, laptops) without copying the persist partition; a factory reset or re-flash that cleared /persist/comma; a partially provisioned device where only the .pub file exists (get_key_pair requires both files).
Related errors
- AuthenticateServer rejected by eUICC: {AUTH_SERVER_ERROR_COD
- 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/654298a0c49e90cb.
Report an issue: GitHub.