Hazel

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:

  1. Catches any error
  2. Logs the error with context
  3. 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 })

On this page