OtterMind/Chat2DB · error · RequestError
unauthorized
Error message
unauthorized
What it means
Raised by RelayHandler._authorize (relay_server.py:197) as a RequestError(HTTP 401) when the request's Authorization header does not equal "Bearer <RELAY_TOKEN>". Comparison uses hmac.compare_digest (constant-time) to avoid timing leaks. The notifier's QQ_RELAY_TOKEN must match the relay's RELAY_TOKEN exactly.
Source
Thrown at script/github/qq_relay/relay_server.py:197
class RelayHandler(BaseHTTPRequestHandler):
relay_state: RelayState
server_version = "Chat2DBQQRelay/1.0"
def _send_json(self, status: int, payload: Mapping[str, Any]) -> None:
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.send_header("X-Content-Type-Options", "nosniff")
self.end_headers()
self.wfile.write(body)
def _authorize(self) -> None:
expected = f"Bearer {self.relay_state.config.relay_token}"
supplied = self.headers.get("Authorization", "")
if not hmac.compare_digest(supplied, expected):
raise RequestError(HTTPStatus.UNAUTHORIZED, "unauthorized")
def _read_payload(self) -> Mapping[str, Any]:
content_type = self.headers.get("Content-Type", "")
if not content_type.lower().startswith("application/json"):
raise RequestError(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Content-Type must be JSON")
try:
content_length = int(self.headers.get("Content-Length", ""))
except ValueError as error:
raise RequestError(HTTPStatus.LENGTH_REQUIRED, "Content-Length is required") from error
if content_length < 1 or content_length > MAX_REQUEST_BYTES:
raise RequestError(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, "request body is too large")
try:
payload = json.loads(self.rfile.read(content_length).decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as error:
raise RequestError(HTTPStatus.BAD_REQUEST, "request body is not valid JSON") from error
if not isinstance(payload, Mapping):
raise RequestError(HTTPStatus.BAD_REQUEST, "request body must be a JSON object")
return payloadView on GitHub (pinned to 5ee1e990e7)
Solutions
- Synchronize QQ_RELAY_TOKEN in GitHub Actions with RELAY_TOKEN on the relay (both >=32 chars, identical).
- Strip trailing whitespace/newlines when storing the secret.
- Test with curl using the exact header: -H "Authorization: Bearer $RELAY_TOKEN".
- Confirm the scheme is Bearer, not Basic.
Example fix
curl -X POST https://relay.example.com/v1/qq/github \
-H "Authorization: Bearer $RELAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repository":"OtterMind/Chat2DB","delivery_id":"t1","message":"hi"}' Defensive patterns
Strategy: validation
Validate before calling
import hmac
expected = f"Bearer {relay_token}"
if not hmac.compare_digest(supplied_auth_header, expected):
raise PermissionError("Authorization token mismatch") Try / catch
from urllib.error import HTTPError
try:
response = send_relay_message(relay_url, relay_token, repository, delivery_id, message)
except HTTPError as error:
if error.code == 401:
# rotate/sync tokens; do not retry with the same token
raise RuntimeError("sync QQ_RELAY_TOKEN with RELAY_TOKEN")
raise Prevention
- Keep QQ_RELAY_TOKEN and RELAY_TOKEN identical and >=32 chars.
- Strip trailing newlines/whitespace when storing secrets.
- Always use the Bearer scheme.
When it happens
Trigger: A POST to /v1/qq/github with a missing, malformed, or wrong Bearer token. The bundled notifier sends the right header only if QQ_RELAY_TOKEN equals the server's RELAY_TOKEN.
Common situations: QQ_RELAY_TOKEN (client) and RELAY_TOKEN (server) differ or were rotated on one side only; the token has trailing whitespace/newline when stored as a secret; sending a request without the Authorization header (e.g. curl test); a wrong scheme (Basic instead of Bearer).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- RELAY_TOKEN must contain at least 32 characters
- ONEBOT_TOKEN must contain at least 32 characters
- Content-Type must be JSON
- Content-Length is required
- request body is too large
AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14).
Data as JSON: /api/errors/531eb137207e0d0d.
Report an issue: GitHub.