Hierarchy
NevoError
├── AuthError # 401 — token invalid
├── ConnectionError # WS couldn't connect after retries
├── HandlerTimeoutError # handler exceeded handler_timeout
└── ReplyError # anything reply-related
├── UnsupportedChannelError
├── NetworkError # transient
├── ServerError # transient (5xx)
└── RequestError # permanent (4xx / validation)
Catching strategy
from nevo import NevoError, AuthError, ReplyError
try:
await client.run()
except AuthError:
# Fix the key and restart.
...
except NevoError as exc:
# Catch-all — log and exit.
...
For replies specifically:
try:
await event.reply(text="…")
except ReplyError as exc:
if exc.is_transient:
# Enqueue for retry.
...
else:
# Log and drop — retrying won't help.
...
Error metadata
Every ReplyError carries:
exc.status_code # int | None — HTTP status from the server
exc.code # str | None — server-provided error code (e.g. "channel_unsupported")
exc.request_id # str | None — X-Request-ID for support correlation
exc.is_transient # bool — True for NetworkError / ServerError
str(exc) includes all of these when set:
invalid api key | status=401 | code=unauthorized | request_id=req-abc123
Token redaction
The SDK never includes the bearer token in exception messages, logs, or tracebacks. The Authorization header is held inside the httpx.AsyncClient and isn’t copied onto exceptions.
Handler exceptions
If your handler raises, the SDK logs the traceback at ERROR and continues consuming the stream. The event is not re-delivered — Nevo’s redelivery is dashboard-driven replay, not client-side retry. Structure your handler to be idempotent if that matters.