🎯

error-handling

🎯Skill

from j-morgan6/elixir-claude-optimization

VibeIndex|
What it does

Handles Elixir errors robustly using tagged tuples, with statements, try/rescue, bang functions, and pattern matching for graceful error management.

πŸ“¦

Part of

j-morgan6/elixir-claude-optimization(7 items)

error-handling

Installation

Add MarketplaceAdd marketplace to Claude Code
/plugin marketplace add j-morgan6/elixir-claude-optimization
Install PluginInstall plugin from marketplace
/plugin install elixir-optimization --scope user
Claude CodeAdd plugin in Claude Code
/plugin list
Install ScriptRun install script
curl -sL https://raw.githubusercontent.com/j-morgan6/elixir-claude-optimization/main/install.sh | bash
git cloneClone repository
git clone https://github.com/j-morgan6/elixir-claude-optimization.git
πŸ“– Extracted from docs: j-morgan6/elixir-claude-optimization
3Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Use when handling errors in Elixir. Covers tagged tuples, with statements, try/rescue, bang functions, supervision trees, and error boundaries in LiveView.

Overview

# Elixir Error Handling Patterns

Tagged Tuples

The idiomatic way to handle success and failure in Elixir.

```elixir

def fetch_user(id) do

case Repo.get(User, id) do

nil -> {:error, :not_found}

user -> {:ok, user}

end

end

# Usage

case fetch_user(123) do

{:ok, user} -> IO.puts("Found: #{user.name}")

{:error, :not_found} -> IO.puts("User not found")

end

```

With Statement

Chain operations that return tagged tuples. Stops at first error.

```elixir

def create_post(user_id, params) do

with {:ok, user} <- fetch_user(user_id),

{:ok, validated} <- validate_params(params),

{:ok, post} <- insert_post(user, validated) do

{:ok, post}

else

{:error, :not_found} -> {:error, :user_not_found}

{:error, %Ecto.Changeset{}} -> {:error, :invalid_params}

error -> error

end

end

```

With Statement - Inline Error Handling

Handle specific errors in the else block.

```elixir

def transfer_money(from_id, to_id, amount) do

with {:ok, from_account} <- get_account(from_id),

{:ok, to_account} <- get_account(to_id),

:ok <- validate_balance(from_account, amount),

{:ok, _} <- debit(from_account, amount),

{:ok, _} <- credit(to_account, amount) do

{:ok, :transfer_complete}

else

{:error, :insufficient_funds} ->

{:error, "Not enough money in account"}

{:error, :not_found} ->

{:error, "Account not found"}

error ->

{:error, "Transfer failed: #{inspect(error)}"}

end

end

```

Case Statements

Pattern match on results.

```elixir

def process_upload(file) do

case save_file(file) do

{:ok, path} ->

Logger.info("File saved to #{path}")

create_record(path)

{:error, :invalid_format} ->

{:error, "File format not supported"}

{:error, reason} ->

Logger.error("Upload failed: #{inspect(reason)}")

{:error, "Upload failed"}

end

end

```

Bang Functions

Functions ending with ! raise errors instead of returning tuples.

```elixir

# Returns {:ok, user} or {:error, changeset}

def create_user(attrs) do

%User{}

|> User.changeset(attrs)

|> Repo.insert()

end

# Returns user or raises

def create_user!(attrs) do

%User{}

|> User.changeset(attrs)

|> Repo.insert!()

end

# Usage

try do

user = create_user!(invalid_attrs)

IO.puts("Created #{user.name}")

rescue

e in Ecto.InvalidChangesetError ->

IO.puts("Failed: #{inspect(e)}")

end

```

Try/Rescue

Catch exceptions when needed (use sparingly).

```elixir

def parse_json(string) do

try do

{:ok, Jason.decode!(string)}

rescue

Jason.DecodeError -> {:error, :invalid_json}

end

end

```

Try/Catch

Handle thrown values (rare).

```elixir

def risky_operation do

try do

do_something()

:ok

catch

:throw, value -> {:error, value}

:exit, reason -> {:error, {:exit, reason}}

end

end

```

Supervision Trees

Let processes fail and restart (preferred over defensive coding).

```elixir

defmodule MyApp.Application do

use Application

def start(_type, _args) do

children = [

MyApp.Repo,

MyAppWeb.Endpoint,

{MyApp.Worker, []}

]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]

Supervisor.start_link(children, opts)

end

end

```

GenServer Error Handling

Handle errors in GenServer callbacks.

```elixir

def handle_call(:risky_operation, _from, state) do

case perform_operation() do

{:ok, result} ->

{:reply, {:ok, result}, update_state(state, result)}

{:error, reason} ->

Logger.error("Operation failed: #{inspect(reason)}")

{:reply, {:error, reason}, state}

end

end

# Let it crash for unexpected errors

def handle_cast(:dangerous_work, state) do

# If this raises, supervisor will restart the process

result = dangerous_function!()

{:noreply, Map.put(state, :result, result)}

end

```

Validation Errors

Return clear, actionable error messages.

```elixir

def validate_image_upload(file) do

with :ok <- validate_file_type(file),

:ok <- validate_file_size(file),

:ok <- validate_dimensions(file) do

{:ok, file}

else

{:error, :invalid_type} ->

{:error, "Only JPEG, PNG, and GIF files are allowed"}

{:error, :too_large} ->

{:error, "File must be less than 10MB"}

{:error, :invalid_dimensions} ->

{:error, "Image must be at least 100x100 pixels"}

end

end

```

Multiple Error Types

Use atoms or custom structs for different error categories.

```elixir

def process_request(params) do

with {:ok, validated} <- validate(params),

{:ok, authorized} <- authorize(validated),

{:ok, result} <- execute(authorized) do

{:ok, result}

else

{:error, :validation, details} ->

{:error, :bad_request, details}

{:error, :unauthorized} ->

{:error, :forbidden, "Access denied"}

{:error, :not_found} ->

{:error, :not_found, "Resource not found"}

{:error, reason} ->

Logger.error("Unexpected error: #{inspect(reason)}")

{:error, :internal_server_error, "Something went wrong"}

end

end

```

Changeset Errors

Extract and format Ecto changeset errors.

```elixir

def changeset_errors(changeset) do

Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->

Enum.reduce(opts, msg, fn {key, value}, acc ->

String.replace(acc, "%{#{key}}", to_string(value))

end)

end)

end

# Usage

case create_user(attrs) do

{:ok, user} -> {:ok, user}

{:error, changeset} ->

errors = changeset_errors(changeset)

{:error, errors}

end

```

Logging Errors

Log errors with appropriate levels.

```elixir

require Logger

def process_item(item) do

case dangerous_operation(item) do

{:ok, result} ->

Logger.info("Processed item #{item.id}")

{:ok, result}

{:error, reason} ->

Logger.error("Failed to process #{item.id}: #{inspect(reason)}")

{:error, reason}

end

end

```

Default Values

Use pattern matching or || for default values.

```elixir

def get_config(key, default \\ nil) do

case Application.get_env(:my_app, key) do

nil -> default

value -> value

end

end

# Or simpler

def get_config(key, default \\ nil) do

Application.get_env(:my_app, key) || default

end

```

Early Returns

Use pattern matching in function heads for early returns.

```elixir

def process_data(nil), do: {:error, :no_data}

def process_data([]), do: {:error, :empty_list}

def process_data(data) when is_list(data) do

# Process the list

{:ok, Enum.map(data, &transform/1)}

end

```

Error Boundaries in LiveView

Handle errors gracefully in Phoenix LiveView.

```elixir

def handle_event("save", params, socket) do

case save_record(params) do

{:ok, record} ->

socket =

socket

|> put_flash(:info, "Saved successfully")

|> assign(:record, record)

{:noreply, socket}

{:error, %Ecto.Changeset{} = changeset} ->

socket =

socket

|> put_flash(:error, "Please correct the errors")

|> assign(:changeset, changeset)

{:noreply, socket}

{:error, reason} ->

socket = put_flash(socket, :error, "An error occurred: #{reason}")

{:noreply, socket}

end

end

```

Avoid Defensive Programming

Don't check for things that can't happen. Let it crash.

Bad (defensive):

```elixir

def get_username(user) do

if user && user.name do

user.name

else

"Unknown"

end

end

```

Good (trust your types):

```elixir

def get_username(%User{name: name}), do: name

```

If the user is nil or doesn't have a name, it's a bug that should crash and be fixed.