Error Handling
Error hierarchy
Section titled “Error hierarchy”All Skytale SDK errors inherit from a base class (SkytaleError in Python, SkytaleError in TypeScript). Every error carries three structured attributes:
| Attribute | Python | TypeScript | Description |
|---|---|---|---|
| Error code | e.code | e.code | Machine-readable identifier (e.g. "auth_failed") |
| HTTP status | e.http_status | e.httpStatus | HTTP status code when applicable (e.g. 401) |
| Documentation | e.doc_url | e.docUrl | Link to relevant troubleshooting docs |
Error code reference
Section titled “Error code reference”AuthError
Section titled “AuthError”| Code | HTTP Status | Description | Recovery |
|---|---|---|---|
auth_failed | 401 | API key invalid or JWT expired | Check SKYTALE_API_KEY, generate a new key with skytale signup |
auth_key_invalid | 401 | Malformed API key | Key must start with sk_live_ or sk_test_ |
api_key_required | — | Operation requires API key but none configured | Pass api_key to constructor or set SKYTALE_API_KEY env var |
unauthorized | 403 | Valid key but insufficient permissions | Check that the key has access to the requested resource |
TransportError
Section titled “TransportError”| Code | HTTP Status | Description | Recovery |
|---|---|---|---|
transport_error | — | Generic connection failure | Check relay URL and network connectivity |
connection_error | — | Cannot establish connection to relay | Verify relay is running, check firewall rules (TCP 5000, UDP 4433) |
join_timeout | — | join_with_token() timed out | Ensure channel owner is running with API key; increase timeout |
listener_died | — | Background listener thread crashed | Close and recreate SkytaleChannelManager; check relay connectivity |
ChannelError
Section titled “ChannelError”| Code | HTTP Status | Description | Recovery |
|---|---|---|---|
channel_error | 400 | Invalid channel name format | Use org/namespace/service format (3 components, lowercase) |
not_found | 404 | Channel does not exist | Verify spelling; create the channel first |
bad_request | 400 | Invalid request parameters | Check method arguments |
MlsError
Section titled “MlsError”| Code | HTTP Status | Description | Recovery |
|---|---|---|---|
mls_error | — | Generic MLS failure | Check data_dir persistence; see troubleshooting |
decryption_failure | — | Cannot decrypt incoming message | MLS state may be stale; rejoin channel with new invite token |
invalid_welcome | — | MLS Welcome message is corrupt or expired | Request a new invite token from the channel owner |
epoch_mismatch | — | Agent’s MLS epoch doesn’t match the group | Another agent with the same identity may exist; use unique identities |
QuotaExceededError
Section titled “QuotaExceededError”| Code | HTTP Status | Description | Recovery |
|---|---|---|---|
quota_exceeded | 429 | Monthly message limit reached | Wait for reset or upgrade plan via skytale billing upgrade |
Catching errors
Section titled “Catching errors”from skytale_sdk import SkytaleChannelManagerfrom skytale_sdk.errors import ( SkytaleError, AuthError, TransportError, ChannelError, MlsError, QuotaExceededError,)
mgr = SkytaleChannelManager(identity=b"my-agent")
try: mgr.create("org/ns/chan") mgr.send("org/ns/chan", "hello") msgs = mgr.receive("org/ns/chan")except AuthError as e: # API key missing or invalid print(f"Authentication failed [{e.code}]: {e}") print(f"HTTP status: {e.http_status}") print(f"See: {e.doc_url}")except TransportError as e: # Relay unreachable or listener died print(f"Transport error [{e.code}]: {e}") if e.code == "listener_died": mgr.close() # Recreate to recoverexcept ChannelError as e: # Bad channel name or channel not found print(f"Channel error [{e.code}]: {e}")except MlsError as e: # Encryption/decryption failure print(f"MLS error [{e.code}]: {e}") print("Check that data_dir is persistent and not shared between agents")except QuotaExceededError as e: # Message limit hit print(f"Quota exceeded [{e.code}]: {e}") print("Upgrade your plan or wait for the monthly reset")except SkytaleError as e: # Catch-all for any other Skytale error print(f"Skytale error [{e.code}]: {e}")import { SkytaleChannelManager, SkytaleError, AuthError, TransportError, ChannelError, MlsError, QuotaExceededError,} from "@skytalesh/sdk";
const mgr = new SkytaleChannelManager({ identity: "my-agent" });
try { await mgr.create("org/ns/chan"); mgr.send("org/ns/chan", "hello"); const msgs = await mgr.receive("org/ns/chan");} catch (e) { if (e instanceof AuthError) { // API key missing or invalid console.log(`Authentication failed [${e.code}]: ${e.message}`); console.log(`HTTP status: ${e.httpStatus}`); console.log(`See: ${e.docUrl}`); } else if (e instanceof TransportError) { // Relay unreachable or listener died console.log(`Transport error [${e.code}]: ${e.message}`); if (e.code === "listener_died") { mgr.close(); // Recreate to recover } } else if (e instanceof ChannelError) { // Bad channel name or channel not found console.log(`Channel error [${e.code}]: ${e.message}`); } else if (e instanceof MlsError) { // Encryption/decryption failure console.log(`MLS error [${e.code}]: ${e.message}`); console.log("Check that dataDir is persistent and not shared between agents"); } else if (e instanceof QuotaExceededError) { // Message limit hit console.log(`Quota exceeded [${e.code}]: ${e.message}`); console.log("Upgrade your plan or wait for the monthly reset"); } else if (e instanceof SkytaleError) { // Catch-all for any other Skytale error console.log(`Skytale error [${e.code}]: ${e.message}`); }}Recovery strategies
Section titled “Recovery strategies”Retry transient failures
Section titled “Retry transient failures”TransportError and server-side 5xx errors are often transient. The SDK retries transient API failures automatically (default: 2 retries with exponential backoff), but you can add application-level retry logic for critical operations:
import timefrom skytale_sdk.errors import TransportError
def send_with_retry(mgr, channel, message, max_attempts=5): for attempt in range(max_attempts): try: mgr.send(channel, message) return except TransportError: if attempt == max_attempts - 1: raise time.sleep(2 ** attempt) # Exponential backoffimport { TransportError } from "@skytalesh/sdk";
async function sendWithRetry( mgr: SkytaleChannelManager, channel: string, message: string, maxAttempts = 5,) { for (let attempt = 0; attempt < maxAttempts; attempt++) { try { mgr.send(channel, message); return; } catch (e) { if (e instanceof TransportError && attempt < maxAttempts - 1) { await new Promise((r) => setTimeout(r, 2 ** attempt * 1000)); } else { throw e; } } }}Recover from MLS state loss
Section titled “Recover from MLS state loss”If an agent loses its data_dir (container restart without a volume, disk failure), it cannot decrypt channel messages anymore. The only recovery path is to rejoin:
from skytale_sdk.errors import MlsError
try: msgs = mgr.receive("org/ns/chan")except MlsError: # MLS state is corrupt — rejoin the channel mgr.close() mgr = SkytaleChannelManager( identity=b"my-agent", data_dir="/var/lib/myagent/skytale", ) mgr.join_with_token("org/ns/chan", new_invite_token)Handle quota limits gracefully
Section titled “Handle quota limits gracefully”For free-tier agents, implement a fallback when the quota is exceeded:
from skytale_sdk.errors import QuotaExceededError
try: mgr.send("org/ns/chan", "status update")except QuotaExceededError: # Queue the message for later, or notify the operator pending_queue.append(("org/ns/chan", "status update")) notify_operator("Skytale quota exceeded — messages queued")Best practices
Section titled “Best practices”-
Always catch
SkytaleErroras a fallback. New error codes may be added in future SDK versions. A catch-all ensures your agent doesn’t crash on unknown errors. -
Log the
codeanddoc_urlfields. Machine-readable codes are stable across SDK versions and useful for monitoring/alerting. -
Don’t catch and ignore
MlsError. MLS failures usually indicate state corruption that won’t self-heal. Log, alert, and trigger a rejoin flow. -
Use mock mode in tests. Set
mock=TrueorSKYTALE_MOCK=1to avoid transport and auth errors in CI/CD pipelines. -
Validate channel names early. Check the
org/namespace/serviceformat before callingcreate()to avoidChannelErrorat runtime.