Errors
Error types and handling in the Bot SDK
The Bot SDK uses Effect's error model with typed, tagged errors.
Error Types
AuthenticationError
Thrown when bot authentication fails.
class AuthenticationError {
readonly _tag = "AuthenticationError"
readonly message: string
readonly cause: unknown
}Common causes:
- Invalid or expired bot token
- Network issues connecting to auth server
BotStartError
Thrown when the bot fails to start.
class BotStartError {
readonly _tag = "BotStartError"
readonly message: string
readonly cause: unknown
}Common causes:
- Missing configuration
- Failed to connect to services
HandlerError
Thrown when an event handler fails.
class HandlerError {
readonly _tag = "HandlerError"
readonly message: string
readonly eventType: string
readonly cause: unknown
}MessageOperationError
Thrown when message operations fail.
class MessageOperationError {
readonly _tag = "MessageOperationError"
readonly message: string
readonly operation: string // "send" | "reply" | "update" | "delete" | "react"
readonly cause: unknown
}QueueError
Thrown when event queue operations fail.
class QueueError {
readonly _tag = "QueueError"
readonly message: string
readonly cause: unknown
}ShapeStreamError
Thrown when Electric SQL subscription fails.
class ShapeStreamError {
readonly _tag = "ShapeStreamError"
readonly message: string
readonly table: string
readonly cause: unknown
}DispatchError
Thrown when event dispatch fails.
class DispatchError {
readonly _tag = "DispatchError"
readonly message: string
readonly eventType: string
readonly cause: unknown
}ConnectionError
Thrown when connecting to external services fails.
class ConnectionError {
readonly _tag = "ConnectionError"
readonly message: string
readonly service: "redis" | "electric" | "backend"
readonly cause: unknown
readonly retryable = true
}RedisSubscriptionError
Thrown when Redis subscription fails.
class RedisSubscriptionError {
readonly _tag = "RedisSubscriptionError"
readonly message: string
readonly cause: unknown
readonly retryable = true
}TransientError
Thrown for temporary failures that can be retried.
class TransientError {
readonly _tag = "TransientError"
readonly message: string
readonly cause: unknown
readonly retryable = true
}ValidationError
Thrown when schema validation fails.
class ValidationError {
readonly _tag = "ValidationError"
readonly message: string
readonly table: string
readonly cause: unknown
readonly retryable = false
}Error Handling
Using withErrorHandler
The simplest way to handle errors in command handlers:
yield *
bot.onCommand(MyCommand, (ctx) =>
Effect.gen(function* () {
// Handler logic
}).pipe(bot.withErrorHandler(ctx)),
)This:
- Catches any error
- Logs the error with context
- Sends "An unexpected error occurred. Please try again." to the channel
Manual Error Handling
Use Effect's error handling for more control:
yield *
bot.message.send(channelId, content).pipe(
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.logError("Failed to send message", { error })
// Handle the error
}),
),
)Catching Specific Errors
Use Effect.catchTag to handle specific error types:
yield *
bot.message.send(channelId, content).pipe(
Effect.catchTag("MessageOperationError", (error) =>
Effect.gen(function* () {
yield* Effect.logError(`Message ${error.operation} failed: ${error.message}`)
// Retry or notify user
}),
),
)Checking Retryability
Use isRetryable to check if an error can be retried:
import { isRetryable } from "@hazel/bot-sdk"
Effect.catchAll((error) =>
Effect.gen(function* () {
if (isRetryable(error)) {
yield* Effect.logWarning("Retryable error, will retry...")
// The SDK handles retries automatically
} else {
yield* Effect.logError("Non-retryable error", { error })
// Handle permanently failed operation
}
}),
)Automatic Retries
The SDK automatically retries transient errors with exponential backoff:
- Default max retries: 3
- Default base delay: 100ms
- Backoff: exponential with jitter
Configure via dispatcherConfig:
runHazelBot({
config: {
dispatcherConfig: {
maxRetries: 5,
retryBaseDelay: 200,
},
},
// ...
})Error Isolation
Handler errors are isolated - one handler failing doesn't affect others:
// Handler 1 throws an error
yield * bot.onMessage((message) => Effect.fail(new Error("Handler 1 failed")))
// Handler 2 still runs normally
yield * bot.onMessage((message) => Effect.log("Handler 2 still works!"))Logging Errors
Use Effect's structured logging:
yield *
Effect.logError("Operation failed", {
error,
context: {
channelId,
userId,
operation: "send",
},
})For warnings:
yield * Effect.logWarning("Retrying operation", { attempt: 2 })