Custom relay address
One-time enrollment code
@@ -213,7 +213,7 @@ export const SentryNotifications = {
Tick to enable the buttons below. Enrolling shares your phone destinations and alert details with the relay operator. Forget locally does not revoke a copied credential.
- Enroll with custom relay
+ Enroll with custom relay
Rotate credential
Revoke relay access
Forget locally
diff --git a/openpilot/starpilot/sentry_mode/notifications.py b/openpilot/starpilot/sentry_mode/notifications.py
index 83b36cc2d3..59ba8fa688 100644
--- a/openpilot/starpilot/sentry_mode/notifications.py
+++ b/openpilot/starpilot/sentry_mode/notifications.py
@@ -295,7 +295,7 @@ class NotificationOwner:
current = self.state['relayEnrollment']
if automatic and current['state'] in ('enrolling', 'enrolled', 'rotating', 'confirming'):
return self.snapshot()
- if current['state'] not in ('unenrolled', 'revoked', 'actionRequired'):
+ if current['state'] not in ('unenrolled', 'revoked'):
raise ValueError('Revoke or forget the current enrollment first')
url = DEFAULT_RELAY_URL if automatic else payload['url']
if not url:
diff --git a/openpilot/starpilot/sentry_mode/tests/test_relay_enrollment.py b/openpilot/starpilot/sentry_mode/tests/test_relay_enrollment.py
index 37ad04c8bd..c98d884cbb 100644
--- a/openpilot/starpilot/sentry_mode/tests/test_relay_enrollment.py
+++ b/openpilot/starpilot/sentry_mode/tests/test_relay_enrollment.py
@@ -123,6 +123,22 @@ def test_owner_rejects_silent_enrollment_and_host_changes(tmp_path):
owner.close()
+def test_owner_requires_resolving_action_required_relay_before_enrollment(tmp_path):
+ import pytest
+ from types import SimpleNamespace
+ from openpilot.starpilot.sentry_mode.notifications import NotificationOwner
+ owner = NotificationOwner(tmp_path / 'notifications', SimpleNamespace(snapshot=lambda: {'events': []}))
+ try:
+ owner.state['relayEnrollment'] = relay.begin('https://relay.example', 'a' * 64)
+ owner.state['relayEnrollment'].update(state='actionRequired', invitation='')
+ with pytest.raises(ValueError):
+ owner.action({'action': 'relayEnroll', 'confirmed': True})
+ owner.action({'action': 'relayRevoke', 'confirmed': True})
+ assert owner.state['relayEnrollment']['state'] == 'revoking'
+ finally:
+ owner.close()
+
+
def test_rotation_recovers_after_old_credential_overlap_expired():
state = relay.begin('https://relay.example', 'a' * 64)
state.update(state='rotating', invitation='', candidate='b' * 64)