OtterMind/Chat2DB · error · RuntimeError
QQ relay network request failed: {error.reason}
Error message
QQ relay network request failed: {error.reason} What it means
Raised by _post_json (notify_qq.py:534) as a RuntimeError when urlopen raises URLError on all three attempts. Unlike HTTPError (which maps status codes), URLError means the request never reached the server (DNS failure, connection refused, timeout, TLS error). The message embeds error.reason.
Source
Thrown at script/github/notify_qq.py:534
**headers,
}
for attempt in range(3):
request = Request(url, data=body, headers=request_headers, method="POST")
try:
with urlopen(request, timeout=15) as response:
response_body = response.read()
decoded = json.loads(response_body.decode("utf-8")) if response_body else {}
if not isinstance(decoded, dict):
raise RuntimeError("QQ relay response was not a JSON object")
return decoded
except HTTPError as error:
relay_error = _decode_relay_error(error.code, error.read())
if error.code not in TRANSIENT_HTTP_STATUSES or attempt == 2:
raise relay_error from error
except URLError as error:
if attempt == 2:
raise RuntimeError(f"QQ relay network request failed: {error.reason}") from error
time.sleep(2**attempt)
raise AssertionError("unreachable")
def _validated_relay_url(value: str) -> str:
parsed = urlparse(value)
if (
parsed.scheme != "https"
or not parsed.hostname
or parsed.username
or parsed.password
or parsed.fragment
):
raise ConfigurationError("QQ_RELAY_URL must be an HTTPS URL without credentials or a fragment")
return value
View on GitHub (pinned to 5ee1e990e7)
Solutions
- Confirm the relay server is running and the host/port in QQ_RELAY_URL is correct.
- From the runner, verify DNS resolution and TCP connectivity to the relay host:port.
- Check firewall/egress rules and ensure the relay is reachable from GitHub Actions runners (or move it to a public HTTPS endpoint).
- Validate the TLS certificate if the URL is https.
Defensive patterns
Strategy: retry
Validate before calling
import socket
from urllib.parse import urlparse
host = urlparse(relay_url).hostname
try:
socket.gethostbyname(host) # DNS resolves
except OSError:
raise SystemExit(f"relay host {host} does not resolve") Try / catch
from urllib.error import URLError
last = None
for _ in range(3):
try:
response = send_relay_message(relay_url, relay_token, repository, delivery_id, message)
break
except URLError as error:
last = error
else:
raise last # escalate after retries Prevention
- Deploy the relay on a host reachable from GitHub Actions runners over HTTPS.
- Keep DNS valid and the relay service healthy before triggering sends.
- Distinguish URLError (network) from HTTPError (relay responded) when retrying.
When it happens
Trigger: The relay host is unreachable: DNS does not resolve, the port is closed, the connection times out (15s), or TLS handshake fails. Each of the 3 attempts with exponential backoff (1s, 2s) fails before this is raised.
Common situations: QQ_RELAY_URL host typo or unresolvable domain; relay container/service down or not started; firewall or network policy blocking egress; TLS certificate invalid; relay deployed in a private network unreachable from the GitHub Actions runner.
Related errors
- QQ relay response was not a JSON object
- Fetch failed with status ${response.status}
- The response body is empty.
- jdbc.driver.downloadFailed
- Prepared QQ notification repository does not match
AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14).
Data as JSON: /api/errors/8df376cd2d715f80.
Report an issue: GitHub.