usestrix/strix · warning · RelayError
invalid_code
invalid_code
Error message
invalid_code
What it means
RelayError('invalid_code') raised by otp_verify() when POST /api/oss/otp/verify returns 403 — the submitted OTP code does not match the one the relay emailed, or it has expired. This is the single 'wrong code' signal from the verification flow.
Solutions
- Re-enter the code carefully, copying it verbatim from the newest email (watch for similar glyphs like 0/O, 1/l)
- If it may have expired, request a fresh code via otp_start() and use that one within its validity window
- Strip whitespace and normalize case before submitting: code.strip().upper() if codes are case-insensitive per relay format
- Guard against autofill/old codes: always use the most recent email, not a pinned one
Example fix
# before
otp_verify(email, input('code: ')) # pasted with trailing space -> invalid_code
# after
otp_verify(email, input('code: ').strip()) Defensive patterns
Strategy: try-catch
Validate before calling
import re
def plausible_code(code: str) -> bool:
c = code.strip()
return bool(re.fullmatch(r"[0-9A-Za-z]{4,10}", c)) Try / catch
try:
otp_verify(email, code)
except RelayError as e:
if e.code == "invalid_code":
code = prompt("wrong or expired code; re-enter or request a new one").strip()
otp_verify(email, code)
else:
raise Prevention
- Strip whitespace from hand-typed or pasted codes
- Offer a 'request new code' action after 2-3 failed attempts
- Use the newest email; disable autofill of old codes
When it happens
Trigger: Calling otp_verify(email, '000000') with a mistyped or transposed code; submitting after the code's validity window elapsed; re-submiting a code that was already consumed; case/whitespace corruption when the code is copied from the email or typed by hand.
Common situations: User typo entering the 6-digit code; code expired because the user waited too long before entering it; email client rendering the code with hidden characters; autofill inserting an old code from a previous verification.
Related errors
AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15).
Data as JSON: /api/errors/fda5cd85a61b0a58.
Report an issue: GitHub.
Appendix: source
Thrown at strix/interface/viewer/auth.py:196
raise RelayError("unavailable")
def otp_verify(email: str, code: str) -> dict[str, Any]:
"""Verify a code. Returns ``{token, email, expires_at}`` or raises RelayError."""
status, data = _post_json(
"/api/oss/otp/verify",
{"email": email, "code": code},
timeout=_OTP_TIMEOUT,
)
if status == 200 and isinstance(data.get("token"), str):
# A token with no usable expiry cannot unlock history locally (the gate
# fails closed), so treat such a response as a failed verification rather
# than reporting success and then leaving the user stuck unverified.
if parse_expiry(data.get("expires_at")) is None:
raise RelayError("unavailable")
return data
if status == 403:
raise RelayError("invalid_code")
raise RelayError("unavailable")
def feedback_submit(email: str, message: str) -> None:
"""Relay a feedback message + email to Strix. No verification is required;
the email is taken as given. Raises RelayError on failure."""
status, data = _post_json(
"/api/oss/feedback",
{"email": email, "message": message},
timeout=_OTP_TIMEOUT,
)
if status == 200:
return
if status == 429:
raise RelayError("rate_limited")
if status == 400:
code = data.get("error")
if code in ("invalid_email", "invalid_message"):View on GitHub (pinned to 8551339130)