elixir-devElixir/Phoenix development companion. Run and interpret mix test, mix credo, mix dialyzer, mix format. Generate modules following OTP conventions: contexts, schemas, GenServers, supervisors, tasks. Debug compilation errors and warnings. Help with Ecto migrations, queries, changesets, and associations. Use for any Elixir or Phoenix development task including writing modules, fixing tests, refactoring code, or understanding OTP patterns.
Install via ClawdBot CLI:
clawdbot install gchapim/elixir-devSee references/mix-commands.md for full command reference.
# Run all tests
mix test
# Specific file or line
mix test test/my_app/accounts_test.exs:42
# By tag
mix test --only integration
# Failed only (requires --failed flag from prior run)
mix test --failed
# With coverage
mix test --cover
Interpreting failures:
** (MatchError) — Pattern match failed; check return value shape.** (Ecto.NoResultsError) — Repo.get! with non-existent ID; use Repo.get or seed data.** (DBConnection.OwnershipError) — Missing async: true or sandbox setup.no function clause matching — Wrong arity or unexpected arg type.mix credo --strict
mix credo suggest --format json
mix credo explain MyApp.Module # Explain issues for specific module
Common Credo fixes:
Credo.Check.Readability.ModuleDoc — Add @moduledoc.Credo.Check.Refactor.CyclomaticComplexity — Extract helper functions.Credo.Check.Design.TagTODO — Address or remove TODO comments.mix dialyzer
mix dialyzer --format short
Common Dialyzer warnings:
The pattern can never match — Dead code or wrong type in pattern.Function has no local return — Crashes on all paths; check internal calls.The call will never return — Calling a function that always raises.@spec annotations; use @dialyzer {:nowarn_function, func: arity} as last resort.mix format
mix format --check-formatted # CI mode — exit 1 if unformatted
Always include @moduledoc, @doc, and @spec on public functions.
defmodule MyApp.Notifications do
@moduledoc """
Manages notification delivery and preferences.
"""
import Ecto.Query
alias MyApp.Repo
alias MyApp.Notifications.Notification
@doc "List notifications for a user, most recent first."
@spec list_notifications(String.t(), keyword()) :: [Notification.t()]
def list_notifications(user_id, opts \\ []) do
limit = Keyword.get(opts, :limit, 50)
Notification
|> where(user_id: ^user_id)
|> order_by(desc: :inserted_at)
|> limit(^limit)
|> Repo.all()
end
end
defmodule MyApp.Notifications.Notification do
@moduledoc """
Schema for push/email/sms notifications.
"""
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@timestamps_opts [type: :utc_datetime_usec]
schema "notifications" do
field :channel, Ecto.Enum, values: [:push, :email, :sms]
field :title, :string
field :body, :string
field :delivered_at, :utc_datetime_usec
field :user_id, :binary_id
timestamps()
end
@required ~w(channel title body user_id)a
@doc false
def changeset(notification, attrs) do
notification
|> cast(attrs, @required ++ [:delivered_at])
|> validate_required(@required)
|> validate_length(:title, max: 255)
end
end
See references/otp-patterns.md for GenServer, Supervisor, Agent, Task patterns.
| Pattern | Use When |
|---------|----------|
| GenServer | Stateful process with sync/async calls (cache, rate limiter, connection pool) |
| Agent | Simple state wrapper with no complex logic |
| Task | One-off async work, fire-and-forget or awaited |
| Task.Supervisor | Supervised fire-and-forget tasks |
| Supervisor | Managing child process lifecycles |
| Registry | Process lookup by name/key |
| DynamicSupervisor | Starting children at runtime |
defmodule MyApp.RateLimiter do
@moduledoc "Token bucket rate limiter."
use GenServer
# Client API
def start_link(opts) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
@spec check_rate(String.t()) :: :ok | {:error, :rate_limited}
def check_rate(key), do: GenServer.call(__MODULE__, {:check, key})
# Server callbacks
@impl true
def init(opts) do
{:ok, %{limit: Keyword.get(opts, :limit, 100), window_ms: 60_000, buckets: %{}}}
end
@impl true
def handle_call({:check, key}, _from, state) do
now = System.monotonic_time(:millisecond)
{count, state} = increment(state, key, now)
if count <= state.limit, do: {:reply, :ok, state}, else: {:reply, {:error, :rate_limited}, state}
end
defp increment(state, key, now) do
# Implementation
end
end
| Error | Cause | Fix |
|-------|-------|-----|
| module X is not available | Missing dep or typo | Check mix.exs deps, verify module name |
| undefined function X/N | Not imported/aliased | Add import, alias, or full module path |
| (CompileError) redefining module | Duplicate module name | Rename one of them |
| protocol not implemented | Missing protocol impl | Add defimpl for your struct |
| cannot use ^x outside of match | Pin in wrong position | Move to pattern match context |
def list(filters) do
Enum.reduce(filters, base_query(), fn
{:status, val}, q -> where(q, [r], r.status == ^val)
{:since, dt}, q -> where(q, [r], r.inserted_at >= ^dt)
{:search, term}, q -> where(q, [r], ilike(r.name, ^"%#{term}%"))
_, q -> q
end)
|> Repo.all()
end
# Query-time preload (single query with join)
from(p in Post, join: a in assoc(p, :author), preload: [author: a])
# Separate query preload
Post |> Repo.all() |> Repo.preload(:author)
# Nested
Repo.preload(posts, [comments: :author])
from(o in Order,
where: o.tenant_id == ^tenant_id,
group_by: o.status,
select: {o.status, count(o.id), sum(o.amount)}
)
|> Repo.all()
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, items: [], loading: true)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
MyApp.Items.delete_item!(id)
{:noreply, assign(socket, items: MyApp.Items.list_items())}
end
@impl true
def render(assigns) do
~H"""
<div :for={item <- @items}>
<span><%= item.name %></span>
<button phx-click="delete" phx-value-id={item.id}>Delete</button>
</div>
"""
end
end
# Subscribe in mount
def mount(_, _, socket) do
if connected?(socket), do: Phoenix.PubSub.subscribe(MyApp.PubSub, "items")
{:ok, assign(socket, items: list_items())}
end
# Broadcast from context
def create_item(attrs) do
with {:ok, item} <- %Item{} |> Item.changeset(attrs) |> Repo.insert() do
Phoenix.PubSub.broadcast(MyApp.PubSub, "items", {:item_created, item})
{:ok, item}
end
end
# Handle in LiveView
def handle_info({:item_created, item}, socket) do
{:noreply, update(socket, :items, &[item | &1])}
end
Generated Mar 1, 2026
A startup building a real-time collaboration platform using Phoenix LiveView for interactive features. The skill helps generate context modules for user management and notifications, run mix tests to ensure reliability, and debug compilation errors during rapid iteration.
An e-commerce company modernizing its legacy Elixir codebase by refactoring monolithic modules into OTP-compliant GenServers for inventory management and order processing. The skill assists with mix credo checks for code quality and Ecto migrations for database schema updates.
A manufacturing firm implementing an Elixir-based system to handle sensor data streams using supervised tasks and agents. The skill is used to generate schema modules for device telemetry, run dialyzer for type safety, and format code for consistency across distributed teams.
A fintech startup developing a compliance monitoring application with Phoenix for reporting dashboards. The skill helps create OTP supervisors for fault-tolerant transaction logging, write Ecto queries for audit trails, and interpret test failures to meet regulatory standards.
A healthcare provider building a patient scheduling system using Elixir for backend APIs and real-time updates. The skill aids in generating context modules for appointment management, running mix format for clean code, and fixing common compilation errors during integration with external EHR systems.
Offer the Elixir Dev skill as part of a premium developer toolkit subscription, providing ongoing updates for mix commands and OTP patterns. Revenue is generated through monthly or annual fees from software teams using it to accelerate Phoenix project development.
Provide expert consulting services where the skill is used to train development teams in Elixir best practices, such as module generation and debugging. Revenue comes from hourly rates or fixed-price contracts for code reviews and workshop sessions.
License the skill to large enterprises for internal use in building scalable Elixir applications, with custom integrations for their specific OTP and Ecto needs. Revenue is generated through one-time licensing fees or tiered pricing based on usage scale.
💬 Integration Tip
Integrate the skill into CI/CD pipelines by using mix format --check-formatted for code style enforcement and mix test --cover for automated testing, ensuring consistent quality across development cycles.
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
Provides a 7-step debugging protocol plus language-specific commands to systematically identify, verify, and fix software bugs across multiple environments.
A comprehensive skill for using the Cursor CLI agent for various software engineering tasks (updated for 2026 features, includes tmux automation guide).
Write, run, and manage unit, integration, and E2E tests across TypeScript, Python, and Swift using recommended frameworks.
Control and operate Opencode via slash commands. Use this skill to manage sessions, select models, switch agents (plan/build), and coordinate coding through Opencode.
Coding style memory that adapts to your preferences, conventions, and patterns for consistent coding.