5. Error Handling
Handle errors gracefully and provide user feedback
When building bots, errors can occur - API failures, validation issues, or unexpected inputs. Let's handle them gracefully.
Using withErrorHandler
The SDK provides a withErrorHandler utility that:
- Catches any error in your command handler
- Logs the error with context
- Sends a user-friendly message to the channel
yield *
bot.onCommand(
AddTaskCommand,
(ctx) =>
Effect.gen(function* () {
// Your command logic here
tasks.push(ctx.args.title)
yield* bot.message.send(ctx.channelId, `Added: ${ctx.args.title}`)
}).pipe(bot.withErrorHandler(ctx)), // <-- Wrap with error handler
)If anything fails, the user sees:
An unexpected error occurred. Please try again.
Manual Error Handling
For more control, use Effect's error handling:
yield *
bot.onCommand(AddTaskCommand, (ctx) =>
Effect.gen(function* () {
tasks.push(ctx.args.title)
yield* bot.message.send(ctx.channelId, `Added: ${ctx.args.title}`)
}).pipe(
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.logError("Failed to add task", { error })
yield* bot.message.send(
ctx.channelId,
"Sorry, I couldn't add that task. Please try again.",
)
}),
),
),
)Handling Specific Errors
Use Effect.catchTag to handle specific error types:
yield *
bot.message.send(channelId, content).pipe(
Effect.catchTag("HttpClientError", (error) =>
Effect.gen(function* () {
yield* Effect.logError("HTTP error", { error })
// Maybe retry or notify the user
}),
),
)Validation Errors
The SDK validates command arguments automatically. If validation fails, the command handler won't run. To provide custom validation:
yield *
bot.onCommand(AddTaskCommand, (ctx) =>
Effect.gen(function* () {
// Custom validation
if (ctx.args.title.length > 100) {
yield* bot.message.send(ctx.channelId, "Task title too long! Keep it under 100 characters.")
return
}
if (tasks.includes(ctx.args.title)) {
yield* bot.message.send(ctx.channelId, "That task already exists!")
return
}
tasks.push(ctx.args.title)
yield* bot.message.send(ctx.channelId, `Added: ${ctx.args.title}`)
}).pipe(bot.withErrorHandler(ctx)),
)Error Logging
Use Effect's logging for structured error logs:
yield *
Effect.logError("Operation failed", {
error,
context: {
command: ctx.commandName,
userId: ctx.userId,
channelId: ctx.channelId,
},
})Complete Task Bot
Here's the final Task Bot with proper error handling:
import { Effect, Schema } from "effect"
import { Command, CommandGroup, runHazelBot } from "@hazel/bot-sdk"
const tasks: string[] = []
const AddTaskCommand = Command.make("task-add", {
description: "Add a new task",
args: { title: Schema.String },
usageExample: "/task-add Buy groceries",
})
const ListTasksCommand = Command.make("task-list", {
description: "List all tasks",
})
const ClearTasksCommand = Command.make("task-clear", {
description: "Clear all tasks",
})
const commands = CommandGroup.make(AddTaskCommand, ListTasksCommand, ClearTasksCommand)
runHazelBot({
commands,
setup: (bot) =>
Effect.gen(function* () {
yield* Effect.log("Task Bot is starting...")
// Add task with validation and error handling
yield* bot.onCommand(AddTaskCommand, (ctx) =>
Effect.gen(function* () {
// Validate title length
if (ctx.args.title.length > 100) {
yield* bot.message.send(
ctx.channelId,
"Task title too long! Keep it under 100 characters.",
)
return
}
// Check for duplicates
if (tasks.includes(ctx.args.title)) {
yield* bot.message.send(ctx.channelId, "That task already exists!")
return
}
// Add the task
tasks.push(ctx.args.title)
const msg = yield* bot.message.send(ctx.channelId, `Added: ${ctx.args.title}`)
yield* bot.message.react(msg, "✅")
}).pipe(bot.withErrorHandler(ctx)),
)
// List tasks with error handling
yield* bot.onCommand(ListTasksCommand, (ctx) =>
Effect.gen(function* () {
if (tasks.length === 0) {
yield* bot.message.send(ctx.channelId, "No tasks yet! Use /task-add to create one.")
return
}
const list = tasks.map((t, i) => `${i + 1}. ${t}`).join("\n")
yield* bot.message.send(ctx.channelId, `**Tasks (${tasks.length}):**\n${list}`)
}).pipe(bot.withErrorHandler(ctx)),
)
// Clear tasks with error handling
yield* bot.onCommand(ClearTasksCommand, (ctx) =>
Effect.gen(function* () {
const count = tasks.length
if (count === 0) {
yield* bot.message.send(ctx.channelId, "No tasks to clear!")
return
}
const msg = yield* bot.message.send(ctx.channelId, "Clearing tasks...")
tasks.length = 0
yield* bot.message.update(msg, `Cleared ${count} task${count === 1 ? "" : "s"}!`)
yield* bot.message.react(msg, "🗑️")
}).pipe(bot.withErrorHandler(ctx)),
)
// React to task mentions (with error catching)
yield* bot.onMessage((message) =>
Effect.gen(function* () {
if (message.content.toLowerCase().includes("task")) {
yield* bot.message.react(message, "📝")
}
}).pipe(
Effect.catchAll((error) => Effect.logWarning("Failed to react to message", { error })),
),
)
}),
})What's Next?
Congratulations! You've built a complete Task Bot with:
- Message handling and reactions
- Type-safe slash commands
- Message operations (send, reply, update, react)
- Proper error handling